即使你不了解并发编程,你也肯定听过wait和notify,如果你非要说没听过,那么就…那么就…
wait和notify是配合synchronized关键字使用的,如果我们想使用ReentrantLock,那么如何来实现等待和唤醒的功能呢?
答案就是使用Condition,先来一段小demo吧
public class Demo {
static ReentrantLock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
public static void main(String[] args) throws Exception {
new Thread(){
@Override
public void run() {
System.out.println("第一个线程加锁");
lock.lock();
try {
System.out.println("第一个线程释放锁&阻塞");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个线程释放锁");
lock.unlock();
}
}.start();
Thread.sleep(3000);
new Thread(){
@Override
public void run() {
System.out.println("第二个线程加锁");
lock.lock();
System.out.println("二个线程唤醒第一个线程");
condition.signal();
System.out.println("第二个线程释放锁");
lock.unlock();
}
}.start();
}
}
如果你运行这个demo的话,结果是
第一个线程加锁
第一个线程释放锁&阻塞
第二个线程加锁
二个线程唤醒第一个线程
第二个线程释放锁
第一个线程释放锁
那么Condition在底层是如何来实现等待和唤醒的功能的呢?
讲Condition的话依旧是绕不开AQS,如果不了解AQS的话,可以先看我的这篇ReentrantReadWriteLock简析,或者直接看这篇微信公众号的文章
如果你已经了解了AQS的话,我们接着往下看,就拿上面的demo来讲一下流程
首先线程1获取到了锁,之后调用了condition.await()方法,在这个方法中,要做的第一件事,就是跟AQS中的加锁等待队列一样,创建了一个Condition的等待队列,然后将线程1加入到了Condition等待队列中,之后线程1会释放锁并将自己挂起
线程2在线程1释放锁之后获取到了锁,之后调用了condition.signal()方法,这个方法中其实就是将Condition等待队列中的线程1放到了AQS中的加锁等待队列中
通过上面两张图,我们知道了condition的await和signal方法都做了些什么,下面再来梳理一下整个流程
- 线程1获取锁
- 线程1调用condition.await()方法,将自己放入Condition等待队列,同时释放锁并挂起自己
- 线程2获取锁
- 线程2将线程1由Condition等待队列转移到加锁等待队列中
- 线程2释放锁
- 线程2释放了之后,唤醒了在加锁等待队列中的线程1,然后线程1继续执行完自己的逻辑之后释放锁
整个流程走完,就分别对应了demo的每一步的输出内容
其实Condition的原理就是这么简单,里面就是使用了一个Condition的等待队列来存储调用了await方法的线程,当signal唤醒等待中的线程时,就将线程加入到加锁队列中,继续去争抢锁,得到了锁之后再执行被挂起之后的逻辑