相关注释源代码:https://github.com/lhj502819/jdk1.8-source-analysis
为什么会有Condition这个东东?
显示的Lock与synchronized的功效相同,都是为执行逻辑加锁。Object 的监视器方法:wait、notify、notifyAll 应该都不陌生,在多线程使用场景下,必须先使用 synchronized 获取到锁,然后才可以调用 Object 的 wait、notify。
Condition 的使用,相当于用 Lock 替换了 synchronized,然后用 Condition 替换 Object 的监视器方法。具体如何替代的下方将会一一解析。
使用场景
举一个贴近生活的例子吧,例如我们排队去上厕所,每个厕所有专门负责发放卫生纸的,我们通过排队最终获得了锁进入了厕所,但是不巧的是发现忘记带纸,遇到这种事情很无奈,但是也得接受这个事实,这时只能乖乖的出去等着发纸员拿纸来**(Condtion#await)**,但其他人也有没拿纸的,需要等着发纸员把纸给前边的人(也就是进入了Condition条件队列中等待),当然自己再出厕所去拿纸之前还要把锁释放掉,好让后面排队的人进来,在发纸员给了纸之后(条件满足Condition#signal)自己再去厕所门口排队(AQS同步队列),等待获取锁。
//Person线程
//上厕所,获取锁
person.lock();
//获取成功
//发现没带纸
//释放锁去等着发纸员发纸,进入条件队列
person.await();
//拿到纸了
//继续去厕所排队拉粑粑
=============================================
//发纸员线程
//从条件等待队列中叫来第一个人,发给他纸
paper.signal()
示例
class ConditionDemo {
final Lock lock = new ReentrantLock();
//定义两个条件
//当消费者消费之后调用notFull.signal通知生产者队列现在有空余位置了,可以继续消费
//队列无数据的时候,消费者调用notEmpty.await
final Condition notFull = lock.newCondition();
//当队列满的时候,生产者调用notFull.await阻塞生产
//当生产者写入队列数据后,调用notEmpty.signal通知消费者队列里有数据了,可以继续生产
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[20];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
// 当count等于数组的大小时,当前线程等待,直到notFull通知,再进行生产
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
// 当count为0,进入等待,直到notEmpty通知,进行消费。
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
大致的工作流程图如下:
原理拆解
await()方法
与Object#wait方法类似。
大致流程如下:
- 将当前线程加到Condition的条件队列中去,对应我们例子中的去排队和发纸员要纸一样;
- 释放当前线程获取的所有的锁,也就是将AQS的state字段置为0
- 将当前通过LockSupport挂起,等待其他线程调用
#signal/#signalAll
唤醒 - 唤醒后调用
AQS#acquireQueued
尝试获取锁,获取成功则继续执行,获取失败则可能还会被阻塞在AQS的同步队列中,等待后边再次获取锁。AQS的同步队列和Condition的条件队列的区别后边我们会讲解。
详细实现流程如下:
signal()方法
与Object#notify类似
大致流程如下:
- 获取当前Condition条件队列的头节点(等待时间最长的节点);
- 将头节点插入到AQS同步队列的尾部中;
- 将头节点的前一个节点状态设置为SIGNAL,这里唤醒后的节点则会执行await方法的第四步(在节点尝试获取锁时,如果节点不符合获取锁的条件,如果节点的前一个节点状态是SIGNAL,那当前节点则会被阻塞挂起,具体请看AQS#acquiredQueued);
- 返回成功成功
详细实现流程如下:
signalAll方法
与signal
方法不同的是,signalAll会将条件队列中的所有线程按照先后顺序添加到AQS的同步队列中。
每个线程添加之后都会尝试去获取锁,这里如果是使用的非公平锁就无法保证获取锁的先后顺序了。
AQS同步队列与Condition的条件队列的区别
宏观层面
每个线程通过await将自己阻塞后,都会进入Condition的条件队列中,如果被其他线程通过调用signal/signalAll会将当前线程放入AQS的同步队列中(同步队列中的线程才有机会再次获取锁),随后会将线程唤醒。
微观层面
条件队列中的Node与AQS中的Node是同一个Node,但使用的指针不同,因此互不影响。
- AQS同步队列使用pre、next两个变量当做队列的指针,队列是双向的;
- Condition条件队列使用nextWaite当做队列的指针,单向的。