LockSupport与Condition解析
本章我们介绍两个Java 并发包中用于线程协作的工具--LockSupport和Condition
LockSupport:
Java 并发包(java.util.concurrent.locks)
提供了基于许可(permit)的线程阻塞和唤醒机制--LockSupport
对于LockSupport是通过方法park以及unpark来对线程进行阻塞和唤醒的
public static void park() {UNSAFE.park(false, 0L);}public static void unpark(Thread var0) {if (var0 != null) {UNSAFE.unpark(var0);}}
我们可以看出park方法很简单只是调用了Unsafe
的方法park。Unsafe
正如它名字而言是 Java 中一个 高度危险且未被官方正式支持 的类,位于 sun.misc
包下(JDK 9 后移至 jdk.internal.misc
包)。它提供了一系列 直接操作底层资源 的方法,允许开发者绕过 Java 语言的安全机制,直接访问内存、操作线程状态等。所以LockSupport就是基于unsafe类进行包装后的类,将原来的不安全类封装成了一个安全类供开发者使用。对于park方法的两个参数一个是Boolean类型一个是long类型,分别用来表示是否为绝对时间以及阻塞的时长,对于0L则是永久阻塞。
对于unpark方法来说则是多了一个参数Thread,这个参数的作用是用来指定唤醒的线程。为什么park不需要参数而unpark需要参数呢,因为unpark唤醒的都是其他线程,当本线程进入阻塞后则无法自己唤醒自己只能通过其他线程来唤醒自己。
Condition:
Condition
是 Java 并发包(java.util.concurrent.locks
)中的一个接口,用于替代传统的 Object.wait()
、Object.notify()
和 Object.notifyAll()
,提供更灵活、更强大的线程间协作机制。它通常与 Lock
接口配合使用,实现精细化的线程等待和唤醒操作。
也就是说Condition的定位其实与Object.wait类似,都是协助锁来实现线程的协作机制。
特性 | Condition | Object.wait()/notify() |
---|---|---|
锁机制 | 必须与 Lock 显式关联(如 ReentrantLock )。 | 必须在 synchronized 块中调用。 |
等待队列 | 每个 Condition 独立维护一个等待队列,可创建多个条件队列(如 notFull 、notEmpty )。 | 每个对象只有一个等待队列,所有线程共享。 |
唤醒方式 | signal() :唤醒一个等待线程;signalAll() :唤醒所有等待线程。 | notify() :随机唤醒一个线程;notifyAll() :唤醒所有线程。 |
中断支持 | await() 可响应中断(抛出 InterruptedException ),也支持不可中断模式(awaitUninterruptibly() )。 | wait() 只能响应中断(抛出异常),无法禁用。 |
超时机制 | 支持灵活的超时等待(如 await(long, TimeUnit) )。 | 仅支持 wait(long timeout) (毫秒级)。 |
其中Condition接口的实现类则是在AQS内部中,而AQS则赋予了Condition灵魂,下面我们来看看Condition子类ConditionObject的源码。
在ConditionObject中我们从Condition最核心的两个方法await方法和signal方法来说起
public final void await() throws InterruptedException {if (Thread.interrupted()) {throw new InterruptedException();} else {Node var1 = this.addConditionWaiter();long var2 = AbstractQueuedLongSynchronizer.this.fullyRelease(var1);int var4 = 0;while(!AbstractQueuedLongSynchronizer.this.isOnSyncQueue(var1)) {LockSupport.park(this);if ((var4 = this.checkInterruptWhileWaiting(var1)) != 0) {break;}}if (AbstractQueuedLongSynchronizer.this.acquireQueued(var1, var2) && var4 != -1) {var4 = 1;}if (var1.nextWaiter != null) {this.unlinkCancelledWaiters();}if (var4 != 0) {this.reportInterruptAfterWait(var4);}}
}
首先则是线程中断位的判断如果已经中断则直接抛出异常(由此可以看出await方法是可中断的方法)。随后则是调用了addConditionWaiter方法,这个方法的作用就是将当前线程加入到Condition所维持的队列中,在我们解析addConditionWaiter方法之前先看一下Condition的属性
public class ConditionObject implements Condition, Serializable {private static final long serialVersionUID = 1173984872572414699L;private transient Node firstWaiter;private transient Node lastWaiter;private static final int REINTERRUPT = 1;private static final int THROW_IE = -1;。。。。}
可以看出含有两个属性firstWaiter和lastWaiter,这两个属性分别表示的是Condition所维持的链表的表头和表尾,由此我们又可以看出ConditionObject 则是用一个链表来串联起整个队列的。
然后我们开始进入addConditionWaiter方法中来看看是如何加入队列的
private Node addConditionWaiter() {Node var1 = this.lastWaiter;if (var1 != null && var1.waitStatus != -2) {this.unlinkCancelledWaiters();var1 = this.lastWaiter;}Node var2 = new Node(Thread.currentThread(), -2);if (var1 == null) {this.firstWaiter = var2;} else {var1.nextWaiter = var2;}this.lastWaiter = var2;return var2;}
代码可以看出首先将队尾的Node节点取出并且检查状态,如果状态不符合要求则会清除出队列。
之后开始初始化本线程的Node节点,并且放在队尾后面。好了这个方法的大致功能已经捋顺接下来我们看看下面的方法
long var2 = AbstractQueuedLongSynchronizer.this.fullyRelease(var1);int var4 = 0;while(!AbstractQueuedLongSynchronizer.this.isOnSyncQueue(var1)) {LockSupport.park(this);if ((var4 = this.checkInterruptWhileWaiting(var1)) != 0) {break;}}if (AbstractQueuedLongSynchronizer.this.acquireQueued(var1, var2) && var4 != -1) {var4 = 1;}if (var1.nextWaiter != null) {this.unlinkCancelledWaiters();}if (var4 != 0) {this.reportInterruptAfterWait(var4);}
随后就会调用AQS的方法进行锁释放(由于线程已经进入Condition队列中),随后则会一个while循环调用isOnSyncQueue进行校验保证当前Node节点不会在AQS的同步队列,这时候就会有疑惑了为什么要保证不会在AQS的同步队列呢,原因就是当Condition调用signal方法的时候被唤醒的线程会从Condition队列中移除转而放入到AQS维护的CLH同步队列中去。所以这里的循环查看是否在同步队列换个意思就是保证当前Node节点没有被唤醒。
在保证当前没有被唤醒之后则会调用 LockSupport.park(this)来讲当前的线程阻塞。由此可见LockSupport很纯粹也很底层,目的就是为了将当前线程进行阻塞或者唤醒。在进入阻塞之后后续的代码则不会执行而是等到唤醒之后才会执行。
唤醒之后首先查看当前线程的中断位,如果被中断则直接跳出循环。
随后则参与锁的竞争调用了acquireQueued方法表示来占有锁,占有锁成功之后则会执行后续的方法如果后面还有节点那么会遍历清除后面的节点。
最后进行判断如果中断位是否开启并且进行处理中断位
下面举一个常用示例
// 示例:生产者-消费者模型
lock.lock();
try {while (queue.isFull()) {notFull.await(); // 等待队列不满}// await() 返回后,线程已持有锁,继续执行queue.add(item); // 生产元素notEmpty.signal(); // 通知消费者
} finally {lock.unlock();
}
整体流程:
- 开始 → 检查线程中断(若已中断,抛异常)
- 创建节点加入 Condition 队列 → 释放锁
- 循环检查节点是否在同步队列:
- 否 → 调用
park()
阻塞 → 等待唤醒 / 中断 - 是 → 跳出循环
- 否 → 调用
- 重新竞争锁(
acquireQueued
)→ 获取锁后处理中断标记 - 清理 Condition 队列无效节点 → 根据中断状态处理异常或恢复标志
- 方法返回,线程持有锁继续执行后续逻辑
下一章节我们将把最后的signal函数源码解析给讲述完毕