前提
并发编程大师Doug Lea在编写JUC
(java.util.concurrent
)包的时候引入了java.util.concurrent.locks.AbstractQueuedSynchronizer
,其实是Abstract Queued Synchronizer
,也就是"基于队列实现的抽象同步器",一般我们称之为AQS
。其实Doug Lea
大神编写AQS
是有严谨的理论基础的,他的个人博客上有一篇论文《The java.util.concurrent Synchronizer Framewor》,可以在互联网找到相应的译文《JUC同步器框架》,如果想要深入研究AQS
必须要理解一下该论文的内容,然后结合论文内容详细分析一下AQS
的源码实现。本文在阅读AQS
源码的时候选用的JDK
版本是JDK11
。
出于写作习惯,下文会把AbstractQueuedSynchronizer称为AQS、JUC同步器框或者同步器框架。
AQS的主要功能
AQS
是JUC
包中用于构建锁或者其他同步组件(信号量、事件等)的基础框架类。AQS
从它的实现上看主要提供了下面的功能:
- 同步状态的原子性管理。
- 线程的阻塞和解除阻塞。
- 提供阻塞线程的存储队列。
基于这三大功能,衍生出下面的附加功能:
- 通过中断实现的任务取消,此功能基于线程中断实现。
- 可选的超时设置,也就是调用者可以选择放弃等待任务执行完毕直接返回。
- 定义了
Condition接口
,用于支持管程形式的await/signal/signalAll
操作,代替了Object
类基于JNI
提供的wait/notify/notifyAll
。
AQS
还根据同步状态的不同管理方式区分为两种不同的实现:独占状态的同步器和共享状态的同步器。
同步器框架基本原理
《The java.util.concurrent Synchronizer Framework》一文中其实有提及到同步器框架的伪代码:
// acquire操作如下: while (synchronization state does not allow acquire) { enqueue current thread if not already queued; possibly block current thread; } dequeue current thread if it was queued; //release操作如下: update synchronization state; if (state may permit a blocked thread to acquire){ unblock one or more queued threads; } 复制代码
撇脚翻译一下:
// acquire操作如下: while(同步状态申请获取失败){ if(当前线程未进入等待队列){ 当前线程放入等待队列; } 尝试阻塞当前线程; } 当前线程移出等待队列 //release操作如下: 更新同步状态 if(同步状态足够允许一个阻塞的线程申请获取){ 解除一个或者多个等待队列中的线程的阻塞状态; } 复制代码
为了实现上述操作,需要下面三个基本环节的相互协作:
- 同步状态的原子性管理。
- 等待队列的管理。
- 线程的阻塞与解除阻塞。
其实基本原理很简单,但是为了应对复杂的并发场景和并发场景下程序执行的正确性,同步器框架在上面的acquire
操作和release
操作中使用了大量的死循环和CAS
等操作,再加上Doug Lea
喜欢使用单行复杂的条件判断代码,如一个if
条件语句会包含大量操作,AQS
很多时候会让人感觉实现逻辑过于复杂。
同步状态管理
AQS
内部内部定义了一个32
位整型的state
变量用于保存同步状态:
/** * The synchronization state.(同步状态值) */ private volatile int state; // 获取state protected final int getState() { return state; } // 直接覆盖设置state protected final void setState(int newState) { state = newState; } // CAS设置state protected final boolean compareAndSetState(int expect, int update) { return STATE.compareAndSet(this, expect, update); } 复制代码
同步状态state
在不同的实现中可以有不同的作用或者表示意义,这里其实不能单纯把它理解为中文意义上的"状态",它可以代表资源数、锁状态等等,下文遇到具体的场景我们再分析它表示的意义。
CLH队列与变体
CLH
锁即Craig, Landin, and Hagersten (CLH) locks
,因为它底层是基于队列实现,一般也称为CLH
队列锁。CLH
锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。从实现上看,CLH
锁是一种自旋锁,能确保无饥饿性,提供先来先服务的公平性。先看简单的CLH
锁的一个简单实现:
public class CLHLock implements Lock { AtomicReference<QueueNode> tail = new AtomicReference<>(new QueueNode()); ThreadLocal<QueueNode> pred; ThreadLocal<QueueNode> current; public CLHLock() { current = ThreadLocal.withInitial(QueueNode::new); pred = ThreadLocal.withInitial(() -> null); } @Override public void lock() { QueueNode node = current.get(); node.locked = true; QueueNode pred = tail.getAndSet(node); this.pred.set(pred); while (pred.locked) { } } @Override public void unlock() { QueueNode node = current.get(); node.locked = false; current.set(this.pred.get()); } static class QueueNode { boolean locked; } // 忽略其他接口方法的实现 } 复制代码
上面是一个简单的CLH
队列锁的实现,内部类QueueNode
只使用了一个简单的布尔值locked
属性记录了每个线程的状态,如果该属性为true
,则相应的线程要么已经获取到锁,要么正在等待锁,如果该属性为false
,则相应的线程已经释放了锁。新来的想要获取锁的线程必须对tail
属性调用getAndSet()
方法,使得自身成为队列的尾部,同时得到一个指向前驱节点的引用pred
,最后线程所在节点在其前驱节点的locked
属性上自旋,直到前驱节点释放锁。上面的实现是无法运行的,因为一旦自旋就会进入死循环导致CPU
飙升,可以尝试使用下文将要提到的LockSupport
进行改造。
CLH
队列锁本质是使用队列(实际上是单向链表)存放等待获取锁的线程,等待的线程总是在其所在节点的前驱节点的状态上自旋,直到前驱节点释放资源。从实际来看,过度自旋带来的CPU性能损耗比较大,并不是理想的线程等待队列的实现。
基于原始的CLH
队列锁中提供的等待队列的基本原理,AQS
实现一种了CLH锁队列的变体(Variant)。AQS
类的protected
修饰的构造函数里面有一大段注释用于说明AQS
实现的等待队列的细节事项,这里列举几点重要的:
AQS
实现的等待队列没有直接使用CLH
锁队列,但是参考了其设计思路,等待节点会保存前驱节点中线程的信息,内部也会维护一个控制线程阻塞的状态值。- 每个节点都设计为一个持有单独的等待线程并且"带有具体的通知方式"的监视器,这里所谓通知方式就是自定义唤醒阻塞线程的方式而已。
- 一个线程是等待队列中的第一个等待节点的持有线程会尝试获取锁,但是并不意味着它一定能够获取锁成功(这里的意思是存在公平和非公平的实现),获取失败就要重新等待。
- 等待队列中的节点通过
prev
属性连接前驱节点,通过next
属性连接后继节点,简单来说,就是双向链表的设计。 CLH
队列本应该需要一个虚拟的头节点,但是在AQS
中没有直接提供虚拟的头节点,而是延迟到第一次竞争出现的时候懒创建虚拟的头节点(其实也会创建尾节点,初始化时头尾节点是同一个节点)。Condition
(条件)等待队列中的阻塞线程使用的是相同的Node
结构,但是提供了另一个链表用来存放,Condition
等待队列的实现比非Condition
等待队列复杂。
线程阻塞与唤醒
线程的阻塞和唤醒在JDK1.5
之前,一般只能依赖于Object
类提供的wait()
、notify()
和notifyAll()
方法,它们都是JNI
方法,由JVM
提供实现,并且它们必须运行在获取监视器锁的代码块内(synchronized
代码块中),这个局限性先不谈性能上的问题,代码的简洁性和灵活性是比较低的。JDK1.5
引入了LockSupport
类,底层是基于Unsafe
类的park()
和unpark()
方法,提供了线程阻塞和唤醒的功能,它的机制有点像只有一个允许使用资源的信号量java.util.concurrent.Semaphore
,也就是一个线程只能通过park()
方法阻塞一次,只能调用unpark()
方法解除调用阻塞一次,线程就会唤醒(多次调用unpark()
方法也只会唤醒一次),可以想象是内部维护了一个0-1的计数器。
LockSupport
类如果使用得好,可以提供更灵活的编码方式,这里举个简单的使用例子:
public class LockSupportMain implements Runnable { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); private Thread thread; private void setThread(Thread thread) { this.thread = thread; } public static void main(String[] args) throws Exception { LockSupportMain main = new LockSupportMain(); Thread thread = new Thread(main, "LockSupportMain"); main.setThread(thread); thread.start(); Thread.sleep(2000); main.unpark(); Thread.sleep(2000); } @Override public void run() { System.out.println(String.format("%s-步入run方法,线程名称:%s", FORMATTER.format(LocalDateTime.now()), Thread.currentThread().getName())); LockSupport.park(); System.out.println(String.format("%s-解除阻塞,线程继续执行,线程名称:%s", FORMATTER.format(LocalDateTime.now()), Thread.currentThread().getName())); } private void unpark() { LockSupport.unpark(thread); } } // 某个时刻的执行结果如下: 2019-02-25 00:39:57.780-步入run方法,线程名称:LockSupportMain 2019-02-25 00:39:59.767-解除阻塞,线程继续执行,线程名称:LockSupportMain 复制代码
LockSupport
类park()
方法也有带超时的变体版本方法,遇到带超时期限阻塞等待场景下不妨可以使用LockSupport#parkNanos()
。
独占线程的保存
AbstractOwnableSynchronizer
是AQS
的父类,一个同步器框架有可能在一个时刻被某一个线程独占,AbstractOwnableSynchronizer
就是为所有的同步器实现和锁相关实现提供了基础的保存、获取和设置独占线程的功能,这个类的源码很简单:
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable { private static final long serialVersionUID = 3737899427754241961L; protected AbstractOwnableSynchronizer() { } // 当前独占线程的瞬时实例 - 提供Getter和Setter方法 private transient Thread exclusiveOwnerThread; protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; } } 复制代码
它就提供了一个保存独占线程的变量对应的Setter
和Getter
方法,方法都是final
修饰的,子类只能使用不能覆盖。
CLH队列变体的实现
这里先重点分析一下AQS
中等待队列的节点AQS
的静态内部类Node
的源码:
static final class Node { // 标记一个节点处于共享模式下的等待 static final Node SHARED = new Node(); // 标记一个节点处于独占模式下的等待 static final Node EXCLUSIVE = null; // 取消状态 static final int CANCELLED = 1; // 唤醒状态 static final int SIGNAL = -1; // 条件等待状态 static final int CONDITION = -2; // 传播状态 static final int PROPAGATE = -3; // 等待状态,初始值为0,其他可选值是上面的4个值 volatile int waitStatus; // 当前节点前驱节点的引用 volatile Node prev; // 当前节点后继节点的引用 volatile Node next; // 当前节点持有的线程,可能是阻塞中等待唤醒的线程 volatile Thread thread; // 下一个等待节点 Node nextWaiter; // 当前操作的节点是否处于共享模式 final boolean isShared() { return nextWaiter == SHARED; } // 获取当前节点的前驱节点,确保前驱节点必须存在,否则抛出NPE final Node predecessor() { Node p = prev; if (p == null) throw new NullPointerException(); else return p; } // 空节点,主要是首次创建队列的时候创建的头和尾节点使用 Node() {} // 设置下一个等待节点,设置持有线程为当前线程 Node(Node nextWaiter) { this.nextWaiter = nextWaiter; THREAD.set(this, Thread.currentThread()); } // 设置waitStatus,设置持有线程为当前线程 Node(int waitStatus) { WAITSTATUS.set(this, waitStatus); THREAD.set(this, Thread.currentThread()); } // CAS更新waitStatus final boolean compareAndSetWaitStatus(int expect, int update) { return WAITSTATUS.compareAndSet(this, expect, update); } // CAS设置后继节点 final boolean compareAndSetNext(Node expect, Node update) { return NEXT.compareAndSet(this, expect, update); } // 设置前驱节点 final void setPrevRelaxed(Node p) { PREV.set(this, p); } // 下面是变量句柄的实现,在VarHandle出现之前使用的是Unsafe,其实底层还是照样使用Unsafe private static final VarHandle NEXT; private static final VarHandle PREV; private static final VarHandle THREAD; private static final VarHandle WAITSTATUS; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); NEXT = l.findVarHandle(Node.class, "next", Node.class); PREV = l.findVarHandle(Node.class, "prev", Node.class); THREAD = l.findVarHandle(Node.class, "thread", Thread.class); WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } } 复制代码
其中,变量句柄(VarHandle
)是JDK9
引入的新特性,其实底层依赖的还是Unsafe
的方法,笔者认为可以简单理解它为Unsafe
的门面类,而定义的方法基本都是面向变量属性的操作。这里需要关注一下Node
里面的几个属性:
waitStatus
:当前Node
实例的等待状态,可选值有5个。
- 初始值整数0:当前节点如果不指定初始化状态值,默认值就是0,侧面说明节点正在等待队列中处于等待状态。
Node#CANCELLED
整数值1:表示当前节点实例因为超时或者线程中断而被取消,等待中的节点永远不会处于此状态,被取消的节点中的线程实例不会阻塞。Node#SIGNAL
整数值-1:表示当前节点的后继节点是(或即将是)阻塞的(通过LockSupport#park()
),当它释放或取消时,当前节点必须LockSupport#unpark()
它的后继节点。Node#CONDITION
整数值-2:表示当前节点是条件队列中的一个节点,当它转换为同步队列中的节点的时候,状态会被重新设置为0。Node#PROPAGATE
整数值-3:此状态值通常只设置到调用了doReleaseShared()
方法的头节点,确保releaseShared()
方法的调用可以传播到其他的所有节点,简单理解就是共享模式下节点释放的传递标记。
prev
、next
:当前Node
实例的前驱节点引用和后继节点引用。thread
:当前Node
实例持有的线程实例引用。nextWaiter
:这个值是一个比较容易令人生疑的值,虽然表面上它称为"下一个等待的节点",但是实际上它有三种取值的情况。
- 值为静态实例
Node.EXCLUSIVE
(也就是null),代表当前的Node
实例是独占模式。 - 值为静态实例
Node.SHARED
,代表当前的Node
实例是共享模式。 - 值为非
Node.EXCLUSIVE
和Node.SHARED
的其他节点实例,代表Condition等待队列中当前节点的下一个等待节点。
Node
类的等待状态waitStatus
理解起来是十分费劲的,下面分析AQS
其他源码段的时候会标识此状态变化的时机。
其实上面的Node
类可以直接拷贝出来当成一个新建的类,然后尝试构建一个双向链表自行调试,这样子就能深刻它的数据结构。例如:
public class AqsNode { static final AqsNode SHARED = new AqsNode(); static final AqsNode EXCLUSIVE = null; static final int CANCELLED = 1; static final int SIGNAL = -1; static final int CONDITION = -2; static final int PROPAGATE = -3; volatile int waitStatus; volatile AqsNode prev; volatile AqsNode next; volatile Thread thread; AqsNode nextWaiter; final boolean isShared() { return nextWaiter == SHARED; } final AqsNode predecessor() { AqsNode p = prev; if (p == null) throw new NullPointerException(); else return p; } AqsNode() { } AqsNode(AqsNode nextWaiter) { this.nextWaiter = nextWaiter; THREAD.set(this, Thread.currentThread()); } AqsNode(int waitStatus) { WAITSTATUS.set(this, waitStatus); THREAD.set(this, Thread.currentThread()); } final boolean compareAndSetWaitStatus(int expect, int update) { return WAITSTATUS.compareAndSet(this, expect, update); } final boolean compareAndSetNext(AqsNode expect, AqsNode update) { return NEXT.compareAndSet(this, expect, update); } final void setPrevRelaxed(AqsNode p) { PREV.set(this, p); } private static final VarHandle NEXT; private static final VarHandle PREV; private static final VarHandle THREAD; private static final VarHandle WAITSTATUS; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); NEXT = l.findVarHandle(AqsNode.class, "next", AqsNode.class); PREV = l.findVarHandle(AqsNode.class, "prev", AqsNode.class); THREAD = l.findVarHandle(AqsNode.class, "thread", Thread.class); WAITSTATUS = l.findVarHandle(AqsNode.class, "waitStatus", int.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } } public static void main(String[] args) throws Exception { AqsNode head = new AqsNode(); AqsNode next = new AqsNode(AqsNode.EXCLUSIVE); head.next = next; next.prev = head; AqsNode tail = new AqsNode(AqsNode.EXCLUSIVE); next.next = tail; tail.prev = next; List<Thread> threads = new ArrayList<>(); for (AqsNode node = head; node != null; node = node.next) { threads.add(node.thread); } System.out.println(threads); } } // 某次执行的输出: [null, Thread[main,5,main], Thread[main,5,main]] 复制代码
实际上,AQS
中一共存在两种等待队列,其中一种是普通的同步等待队列,这里命名为Sync Queue
,另一种是基于Sync Queue
实现的条件等待队列,这里命名为Condition Queue
。
理解同步等待队列
前面已经介绍完AQS
的同步等待队列节点类,下面重点分析一下同步等待队列的相关源码,下文的Sync队列、Sync Queue、同步队列和同步等待队列是同一个东西。首先,我们通过分析Node
节点得知Sync
队列一定是双向链表,AQS
中有两个瞬时成员变量用来存放头节点和尾节点:
// 头节点引用(注意由transient volatile修饰,不会序列化,并且写操作会马上刷新到主内存) private transient volatile Node head; // 尾节点引用(注意由transient volatile修饰,不会序列化,并且写操作会马上刷新到主内存) private transient volatile Node tail; // 变量句柄相关,用于CAS操作头尾节点 private static final VarHandle STATE; private static final VarHandle HEAD; private static final VarHandle TAIL; static { try { MethodHandles.Lookup l = MethodHandles.lookup(); STATE = l.findVarHandle(AbstractQueuedSynchronizer.class, "state", int.class); HEAD = l.findVarHandle(AbstractQueuedSynchronizer.class, "head", Node.class); TAIL = l.findVarHandle(AbstractQueuedSynchronizer.class, "tail", Node.class); } catch (ReflectiveOperationException e) { throw new ExceptionInInitializerError(e); } // 确保LockSupport类已经初始化 - 这里应该是为了修复之前一个因为LockSupport未初始化导致的BUG Class<?> ensureLoaded = LockSupport.class; } // 初始化同步队列,注意初始化同步队列的时候,头尾节点都是指向同一个新的Node实例 private final void initializeSyncQueue() { Node h; if (HEAD.compareAndSet(this, null, (h = new Node()))) tail = h; } // CAS设置同步队列的尾节点 private final boolean compareAndSetTail(Node expect, Node update) { return TAIL.compareAndSet(this, expect, update); } // 设置头节点,重点注意这里:传入的节点设置成头节点之后,前驱节点和持有的线程会置为null,这是因为: // 1.头节点一定没有前驱节点。 // 2.当节点被设置为头节点,它所在的线程一定是已经解除了阻塞。 private void setHead(Node node) { head = node; node.thread = null; node.prev = null; } 复制代码
当前线程加入同步等待队列和同步等待队列的初始化是同一个方法,前文提到过:同步等待队列的初始化会延迟到第一次可能出现竞争的情况,这是为了避免无谓的资源浪费,具体方法是addWaiter(Node mode)
:
// 添加等待节点到同步等待队列,实际上初始化队列也是这个方法完成的 private Node addWaiter(Node mode) { // 基于当前线程创建一个新节点,节点的模式由调用者决定 Node node = new Node(mode); for (;;) { Node oldTail = tail; // 尾节点不为空说明队列已经初始化过,则把新节点加入到链表中,作为新的尾节点,建立和前驱节点的关联关系 if (oldTail != null) { node.setPrevRelaxed(oldTail); if (compareAndSetTail(oldTail, node)) { oldTail.next = node; return node; } } else { // 尾节点为空说明队列尚未初始化过,进行一次初始化操作 initializeSyncQueue(); } } } 复制代码
在首次调用addWaiter()
方法,死循环至少执行两轮再跳出,因为同步队列必须初始化完成后(第一轮循环),然后再把当前线程所在的新节点实例添加到等待队列中再返(第二轮循环)当前的节点,这里需要注意的是新加入同步等待队列的节点一定是添加到队列的尾部并且会更新AQS
中的tail属性为最新入队的节点实例。
假设我们使用Node.EXCLUSIVE
模式把新增的等待线程加入队列,例如有三个线程分别是thread-1
、thread-2
和thread-3
,线程入队的时候都处于阻塞状态,模拟一下依次调用上面的入队方法的同步队列的整个链表的状态。
先是线程thread-1
加入等待队列:
接着是线程thread-2
加入等待队列:
最后是线程thread-3
加入等待队列:
如果仔细研究会发现,如果所有的入队线程都处于阻塞状态的话,新入队的线程总是添加到队列的tail
节点,阻塞的线程总是"争抢"着成为head
节点,这一点和CLH
队列锁的阻塞线程总是基于前驱节点自旋以获取锁的思路是一致的。下面将会分析的独占模式与共享模式,线程加入等待队列都是通过addWaiter()
方法。
理解条件等待队列
前面已经相对详细地介绍过同步等待队列,在AQS
中还存在另外一种相对特殊和复杂的等待队列-条件等待队列。介绍条件等待队列之前,要先介绍java.util.concurrent.locks.Condition
接口。
public interface Condition { // 当前线程进入等待状态直到被唤醒或者中断 void await() throws InterruptedException; // 当前线程进入等待状态,不响应中断,阻塞直到被唤醒 void awaitUninterruptibly(); // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制 long awaitNanos(long nanosTimeout) throws InterruptedException; // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制 boolean await(long time, TimeUnit unit) throws InterruptedException; // 当前线程进入等待状态直到被唤醒或者中断,阻塞带时间限制 boolean awaitUntil(Date deadline) throws InterruptedException; // 唤醒单个阻塞线程 void signal(); // 唤醒所有阻塞线程 void signalAll(); } 复制代码
Condition
可以理解为Object
中的wait()
、notify()
和notifyAll()
的替代品,因为Object
中的相应方法是JNI
(Native
)方法,由JVM
实现,对使用者而言并不是十分友好(有可能伴随JVM
版本变更而受到影响),而Condition
是基于数据结构和相应算法实现对应的功能,我们可以从源码上分析其实现。
Condition
的实现类是AQS
的公有内部类ConditionObject
。ConditionObject
提供的入队列方法如下:
public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; /** First node of condition queue. */ - 条件队列的第一个节点 private transient Node firstWaiter; /** Last node of condition queue. */ - 条件队列的最后一个节点 private transient Node lastWaiter; // 公有构造函数 public ConditionObject() { } // 添加条件等待节点 private Node addConditionWaiter() { // 这里做一次判断,当前线程必须步入此同步器实例 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); // 临时节点t赋值为lastWaiter引用 Node t = lastWaiter; // If lastWaiter is cancelled, clean out. // 最后一个节点不为条件等待状态,则是取消状态 if (t != null && t.waitStatus != Node.CONDITION) { // 解除所有取消等待的节点的连接 unlinkCancelledWaiters(); t = lastWaiter; } // 基于当前线程新建立一个条件等待类型的节点 Node node = new Node(Node.CONDITION); // 首次创建Condition的时候,最后一个节点临时引用t为null,则把第一个节点置为新建的节点 if (t == null) firstWaiter = node; else // 已经存在第一个节点,则通过nextWaiter连接新的节点 t.nextWaiter = node; // 最后一个节点的引用更新为新节点的引用 lastWaiter = node; return node; } // 从条件等待队列解除所有取消等待的节点的连接,其实就是所有取消节点移除的操作,涉及到双向链表的断链操作、第一个和最后一个节点的引用更新 private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; // 注意这里等待状态的判断 if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; } } // 当前同步器实例持有的线程是否当前线程(currentThread()) protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); } // 暂时不分析其他方法 } 复制代码
实际上,Condition
的所有await()
方法变体都调用addConditionWaiter()
添加阻塞线程到条件队列中。我们按照分析同步等待队列的情况,分析一下条件等待队列。正常情况下,假设有2个线程thread-1
和thread-2
进入条件等待队列,都处于阻塞状态。
先是thread-1
进入条件队列:
然后是thread-2
进入条件队列:
条件等待队列看起来也并不复杂,但是它并不是单独存在和使用的,一般依赖于同步等待队列,下面的一节分析Condition
的实现的时候再详细分析。