一、重入锁ReentrantLock
1. 常用方法
public static ReentrantLock lock = new ReentrantLock();
lock.lock();//获得锁,如果锁已被占用,则等待
lock.lockInterruptibly();//获得锁,但优先响应中断
lock.tryLock();//尝试获得锁,若成功则返回true,若当前锁被其他线程占用,申请锁失败,则返回false,该方法不等待,因此也不会产生死锁
lock.tryLock(long time, TimeUnit unit);//在给定时间内尝试获得锁
lock.unlock();//释放锁
2. 简单的重入锁使用案例
public class Create implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; ++j) {
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException { }
}
3. 和synchronized的对比
和synchronized相比,重入锁有明显的操作过程,开发人员必须手动指定何时加锁,何时释放,因此,重入锁对逻辑控制的灵活性高于synchronized。
对于synchronized来说,若一个线程在等待锁,那么只会有两种情况
1.获得这把锁继续运行
2.保持等待
但是使用重入锁lockInterruptibly,线程则可以在等待过程中被中断
4. 重入的含义
锁可以反复使用,一个线程可以连续两次获得同一把锁,但需要注意的是,如果同一个线程多次获得锁,那么也必须释放相同次数
若释放次数多了,就会得到一个java.lang.IllegalMonitorStateException异常
若释放次数少了,那么相当于线程还持有这个锁,其他线程无法进入临界区
//重入锁实例
lock.lock();
lock.lock();
try{
i++;
}finally {
lock.unlock();
lock.unlock();
}
5. 中断响应 lockInterruptibly
//demo
public class IntLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
lock1.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(Thread.currentThread().getId() + "线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock(1);
IntLock r2 = new IntLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
//线程t1和t2启动后,t1先占用lock1,再占用lock2,t2先占用lock2,再占用lock1.因此两者很容易形成相互等待
//而使用lockInterruptibly,在main函数中对t2进行中断,此时t2会放弃lock1的申请,同时释放已获得的lock2,然后t1就可以获得lock2继续执行下去
6.锁申请等待限时tryLock
//demo
public class IntLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
private static int i = 1;
@Override
public void run() {
try {
while (lock.isLocked()) {
System.out.println(Thread.currentThread().getId() + " 线程被阻塞,等待中,第" + i++ + "次尝试");
Thread.sleep(1000);
}
if (lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getId() + " 获得线程,开始休眠");
Thread.sleep(6000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getId() + " 释放");
if (lock.isHeldByCurrentThread()) lock.unlock();
}
System.out.println(Thread.currentThread().getId() + " 结束");
}
public static void main(String[] args) throws InterruptedException {
IntLock r1 = new IntLock();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
t1.start();
Thread.sleep(1000);
t2.start();
t2.join();
System.out.println("MISSION COMPLETE");
}
}
7. 公平锁
public static ReentrantLock lock = new ReentrantLock(boolean fair);
public static ReentrantLock lock = new ReentrantLock(true);//公平锁
在多数情况下,锁都是非公平的,例如线程1请求了锁A,然后线程2也请求获得锁A,非公平锁是随机分配给1或者2的,但是公平锁则是按照先到先得的理念分配锁,也就是先分配给1然后给2,
公平锁的一大特点是不会产生饥饿,只要你排队就一定能获得资源。虽然公平锁看起来很优美,但是公平锁实现内部必然要维持一个有序队列,因而效率很低,因此若没有特别需求则不需要使用公平锁
二、Condition
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
通过lock接口获得一个Condition对象,就可以让线程在合适的时间等待,或在某特定时刻得到通知,继续执行。
当线程使用 await()方法时,要求线程持有相关的重入锁,在 await()方法调用后,这个线程会释放这个锁,同理, signal()方法调用时,也要求线程先获得相关的锁,在调用完后,一定要释放锁,否则程序无法继续执行
1.基本方法
await();
await(long time, TimeUnit unit);
awaitNanos(long nanoTimeout);
awaitUninterruptibly();
awaitUntil(Date deadline);
signal();//唤醒一个等待中的进程
signalAll();//唤醒所有等待中的进程
2.和wait()、notify()方法的异同
wait()和notify()方法是与synchronized配合使用的,而Condition是与重入锁相关联的
3.demo
public class ReenterLockCondition implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();//当线程使用await()方法时,要求线程持有相关的重入锁
condition.await();//await()方法调用后,这个线程会释放这个锁
System.out.println("Thread is going on");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReenterLockCondition r1 = new ReenterLockCondition();
Thread t1 = new Thread(r1);
t1.start();
Thread.sleep(2000);
lock.lock();//Condition.signal()方法调用时,要求线程先获得相关的锁
condition.signal();
lock.unlock();//在调用完后,一定要释放锁,否则t1无法继续执行
}
}
三、信号量Semaphore
信号量是对锁的扩展,无论是内部锁synchronized或重入锁ReentrantLock,一次都只允许一个线程访问资源,而信号量则可以指定多个线程同时访问某一个资源
1.构造函数
public Semaphore(int permits);
public Semaphore(int permits, boolean fair);//第二个参数指定是否公平
2.基本方法
semp.acquire();//尝试获得准入许可,若无法获得则等待
semp.acquireUninterruptibly();//和acquire相同,但是不响应中断
semp.tryAcquire();//尝试获得,成功返回true,反之返回false,不阻塞
semp.tryAcquire(long timeout,TimeUnit unit);
public void release();//释放一个许可
四、读写锁ReadWriteLock
读不会修改文档,所以所有读线程之间不需要阻塞,而写线程会对文档进行修改,所以读写线程,写写线程之间需要进行阻塞。
1.demo
public class ReadWriteDemo {
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
private int value;
public Object handleRead(Lock lock) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
return value;
} finally {
lock.unlock();
}
}
public void handleWrite(Lock lock, int index) throws InterruptedException {
try {
lock.lock();
Thread.sleep(1000);
value = index;
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
final ReadWriteDemo demo = new ReadWriteDemo();
Runnable readRunnale = new Runnable() {
@Override
public void run() {
try {
demo.handleRead(readLock);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable writeRunnale = new Runnable() {
@Override
public void run() {
try {
demo.handleWrite(writeLock, new Random().nextInt());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 18; i++)
new Thread(readRunnale).start();
for (int i = 18; i < 20; i++)
new Thread(writeRunnale).start();
}
}
五、倒计数器CountDownLatch
CountDownLatch的构造函数接受一个int作为参数,既这个计数器的计数个数
public class test implements Runnable {
static final CountDownLatch latch = new CountDownLatch(10);
static final test t = new test();
@Override
public void run() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++)
exec.submit(t);
latch.await();
System.out.println("Fire");
exec.shutdown();
}
}
六、循环栅栏CyclicBarrier
基础功能和倒计数器相似,可以实现线程间的计数等待,但是功能更加强大
public CyclicBarrier(int parties,Runnable barrierAction);
//parties:计数器个数
//barrierAction:每次计数完成后,系统会执行的动作
实例
public class test {
public static class Worker implements Runnable {
private final CyclicBarrier cyclicBarrier;
private final String id;
Worker(CyclicBarrier cyclicBarrier, String id) {
this.cyclicBarrier = cyclicBarrier;
this.id = id;
}
@Override
public void run() {
try {
cyclicBarrier.await();
command();
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
void command() {
try {
Thread.sleep(new Random().nextInt(10) * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(id + " 号完成任务");
}
}
public static class BarrierRun implements Runnable {
private final int N;
private boolean flag;
public BarrierRun(boolean flag, int N) {
this.N = N;
this.flag = flag;
}
@Override
public void run() {
if (flag) {
flag = false;
System.out.println(N + "个工人,集合完毕");
} else
System.out.println(N + "个工人,任务完成");
}
}
public static void main(String[] args) {
final int N = 5;
Thread[] workers = new Thread[N];
CyclicBarrier cyclic = new CyclicBarrier(N, new BarrierRun(true, N));
for (int x = 0; x < N; x++) {
System.out.println("工人" + x + "报道");
workers[x] = new Thread(new Worker(cyclic, Integer.toString(x)));
workers[x].start();
}
}
}
七、线程阻塞工具LockSupport
LockSupport是一个线程阻塞工具,可以在线程内任意位置让线程阻塞。
和Thread.suspend()方法相比,他弥补了由于resume()方法执行位置错误引发的异常
和Object.wait()方法相比,他不需要先获得某个对象的锁,也不会抛出InterruptedException异常
1.suspend、resume引发的异常
//案例
public class test {
public static final Object u = new Object();
public static class Change extends Thread {
@Override
public void run() {
synchronized (u) {
System.out.println("first");
Thread.currentThread().suspend();
}
}
}
public static void main(String[] args) {
Change change = new Change();
change.start();
change.resume();
}
}
//当我们执行这段代码时,程序会输出first,但是程序不会退出,而是会挂起,这是因为时间顺序的缘故,resume发生在了suspend的前面,导致resume方法未起效。
当我们用LockSupport改写上述程序,虽然我们仍然无法保证unpark发生在park前,但程序将可以正常运转退出。
这是因为LockSupport使用了类似信号量的机制,他为每一个线程准备了一个许可证,默认状况下许可证为false,park为消费许可证,而unpark为使许可证可用,但是与信号量不同,许可证不能累加
//改写后的程序
public class test {
public static final Object u = new Object();
public static class Change extends Thread {
@Override
public void run() {
synchronized (u) {
System.out.println("first");
LockSupport.park();
}
}
}
public static void main(String[] args) {
Change change = new Change();
change.start();
LockSupport.unpark(change);
}
}
2.中断
与其他支持中断函数不同,LockSupport.park()方法不会抛出InterruptedException异常,但是可以从Thread.interrupted()获得中断标记。
八、限流算法
1.漏桶算法
基本思想
利用一个缓存区,当有请求进入系统,无论请求速率如何,都先在缓存区中保存,然后以固定的流速流出缓存区
特点
无论外部压力如何,漏桶算法总是以固定的流速处理数据
2.令牌桶算法
基本思想
在令牌桶中,存放的将不是请求,而是令牌,处理程序只有拿到令牌,才能对请求进行处理,如果没有令牌,处理程序就要丢弃请求或者等待可用令牌,为了限制流速,该算法在每个单位时间内产生一定量的令牌存入桶中。