读写锁的读锁不支持Condition,读写锁的写锁和互斥锁支持Condition。
前言
Condition有两个核心方法await和signal方法,调用时必须获取到Lock锁。
Condition里维护等待队列的是一个单向链表。
阻塞时在等待队列中插入一个condition节点,然后先释放锁再park阻塞。被唤醒时通过查看是否在AQS同步队列中判断是否是被signal唤醒的。是-重新拿锁。
释放时,从等待队列中取头节点,将状态改为初始状态,然后加入到AQS队列中调用unpark唤醒。
一、Condition与Lock的关系
condition本身也是一个接口,其功能和wait/notify类似。
其提供的就两个核心方法,await和signal方法。分别对应着Object的wait和notify方法。调用Object对象的这两个方法,需要在同步代码块里面,即必须先获取到锁才能执行这两个方法。同理,Condition调用这两个方法,也必须先获取到锁。只不过Object的锁是synchronized,而Condition是Lock锁。这里先声明两个概念:
- 等待队列:Condition上用来等待这个条件的队列,这是一个单向链表
- 同步队列:就是AQS等待拿锁的CLH队列,这是一个双向链表
二、Condition实现原理
Condition是一个接口,AQS抽象类中ConditionObject实现了Condition的全部功能。
每一个Condition对象上面都阻塞了多个线程。因此,在ConditionObject内部也有一个单向链表组成的队列。
await()实现分析
public final void await() throws InterruptedException {
if (Thread.interrupted()) //执行await操作时,收到中断信号抛出异常
throw new InterruptedException();
Node node = addConditionWaiter(); //加入Condition的等待队列
int savedState = fullyRelease(node); //阻塞在Condition之前必须先释放锁,否则会死锁
int interruptMode = 0;
//判断Node是否在AQS队列中,初始只在Condition队列中,在执行notify时会放入AQS同步队列
while (!isOnSyncQueue(node)) {
LockSupport.park(this); //自己阻塞自己
//如果中间收到过中断信号返回
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) //重新拿锁
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode); //被中断唤醒,向外抛出中断异常
}
线程调用await的时候,肯定已经先拿到了锁。所以在addConditionWaiter内部的操作不需要CAS,线程天生安全。
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果尾结点被取消,将取消节点释放。
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//新节点,状态为condition = -2
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
final boolean isOnSyncQueue(Node node) {
//节点状态为condition或者节点的前一个节点为空一定不在AQS
//加入AQS队列的一定是初始状态,因为是先CAS成初始状态再插入AQS
//AQS中有一个空的头节点,所以如果在AQS中前节点一定不为空
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) //节点的后继节点不为null,说明节点肯定在队列中,返回true,这里很重要的一点要明白,prev和next都是针对同步队列的节点
return true;
/*
* 当满足了节点状态不为condition且头节点不为空可能还处于enq函数CAS插入AQS的过程中
*/
return findNodeFromTail(node);
}
//是一个兜底的策略,从尾部开始找
private boolean findNodeFromTail(Node node) {
//取得同步队列的队尾元素
Node t = tail;
//无限循环,从队尾元素一直往前找,找到相等的节点就说明节点在队列中,node为null了,说明前面已经没有节点可以找了,那就返回false
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
在线程执行wait操作之前,必须先释放锁,也就是fullyRelease(node),否则会发生死锁。
初始的时候,Node只在Condition的队列里,而不在AQS的队列里。但执行notify操作的时候,会放进AQS的同步队列。
awaitUninterruptibly()实现分析
awaitUninterruptibly不会响应中断,收到中断信号后不会抛出异常而是继续执行。
public final void awaitUninterruptibly() {
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
//重新拿锁过程中收到中断信号返回true
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt(); //中断补偿机制
}
notify实现分析
public final void signal() {
if (!isHeldExclusively()) //只有持有锁的线程才能执行signal
throw new IllegalMonitorStateException();
Node first = firstWaiter; //唤醒头节点,FIFO
if (first != null)
doSignal(first);
}
private void doSignal(Node first) { //唤醒节点
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* node可能正在取消,所以需要CAS,失败说明被取消了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* 将节点放入AQS同步队列中
*/
Node p = enq(node);
int ws = p.waitStatus;
//唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
将状态从condition设置为0,然后先放入AQS中然后再唤醒。所以await中可以通过是否在AQS中来判断是否被unpark唤醒的。