图解 ReentrantLock 的条件变量 Condition 机制
概述
想必大家都使用过 wait()和 notify()这两个方法把,这两个方法主要用于多线程间的协同处理,即控制线程之间的等待、通知、切换及唤醒。而 RenentrantLock 也支持这样条件变量的能力,而且相对于 synchronized 更加强大,能够支持多个条件变量。
最好可以先阅读 ReentrantLock 系列文章:
ReentrantLock 条件变量使用
ReentrantLock 类 API
Condition newCondition()
: 创建条件变量对象
Condition 类 API
void await()
: 当前线程从运行状态进入等待状态,同时释放锁,该方法可以被中断void awaitUninterruptibly()
:当前线程从运行状态进入等待状态,该方法不能够被中断void signal()
: 唤醒一个等待在 Condition 条件队列上的线程void signalAll()
: 唤醒阻塞在条件队列上的所有线程
运行结果:
condition 的 wait 和 notify 必须在 lock 范围内
实现条件变量的等待和唤醒,他们必须是同一个 condition。
线程 1 执行 conidtion.notify()后,并没有释放锁,需要等释放锁后,线程 0 重新获取锁成功后,才能继续向下执行。
图解实现原理
await 过程
线程 0(Thread-0)一开始获取锁,exclusiveOwnerThread 字段是 Thread-0, 如下图中的深蓝色节点
Thread-0 调用 await 方法,Thread-0 封装成 Node 进入 ConditionObject 的队列,因为此时只有一个节点,所有 firstWaiter 和 lastWaiter 都指向 Thread-0,会释放锁资源,NofairSync 中的 state 会变成 0,同时 exclusiveOwnerThread 设置为 null。如下图所示。
线程 1(Thread-1)被唤醒,重新获取锁,如下图的深蓝色节点所示。
Thread-0 被 park 阻塞,如下图灰色节点所示:
源码如下:
下面是 await()方法的整体流程,其中
LockSupport.park(this)
进行阻塞当前线程,后续唤醒,也会在这个程序点恢复执行。
将线程封装成 Node, 加入到 ConditionObject 队列尾部,此时节点的等待状态时-2。
清理条件队列中的 cancel 类型的节点,比如中断、超时等会导致节点转换为 Cancel
fullyRelease 方法将 r 让 Thread-0 释放锁, 这个时候 Thread-1 就会去竞争锁
判断节点是否在 AQS 阻塞对列中,不在条件对列中
signal 过程
Thread-1 执行 signal 方法唤醒条件队列中的第一个节点,即 Thread-0,条件队列置空
Thread-0 的节点的等待状态变更为 0, 重新加入到 AQS 队列尾部。
后续就是 Thread-1 释放锁,其他线程重新抢锁。
源码如下:
signal()方法是唤醒的入口方法
调用 doSignal()方法唤醒节点
调用 transferForSignal()方法,先将节点的 waitStatus 改为 0,然后加入 AQS 阻塞队列尾部,将 Thread-3 的 waitStatus 改为 -1。
总结
本文讲解了 ReentrantLock 中条件变量的使用和原理实现,希望对大家有帮助。
版权声明: 本文为 InfoQ 作者【JAVA旭阳】的原创文章。
原文链接:【http://xie.infoq.cn/article/32c3b0ee8c70d4a19fdf15c33】。文章转载请联系作者。
评论