Condition简介
public class ConditionDemo {
//锁
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] queue;
private final int size;
public ConditionDemo(int size) {
this.queue = new Object[size];
this.size = size;
}
private int count;
private int putIndex;
private int takeIndex;
public void put(T t) throws InterruptedException{
//先加锁
lock.lock();
try{
//queue已满
while(count == size){
//线程阻塞等待
notFull.await();
}
this.queue[putIndex] = t;
if(++putIndex == size){
putIndex = 0;
}
count++;
//唤醒一个等待take()的线程
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException{
//先加锁
lock.lock();
try{
//queue为空
while(count == 0){
//线程阻塞等待
notEmpty.await();
}
T t = (T)this.queue[takeIndex];
if(++takeIndex == size){
takeIndex = 0;
}
count--;
//唤醒一个等待put()的线程
notFull.signal();
return t;
} finally {
lock.unlock();
}
}
}
-
上述示例演示了Condition的使用,无论是put()还是take()都是对共享queue的操作,需要加锁
-
当put()操作发现queue已满时,线程会进入condition队列阻塞自己,对应的正是lock产生的notFull,等待take()线程的唤醒
-
当take()操作发现queue为空时,线程会进入condition队列阻塞自己,对应的正是lock产生的notEmpty,等待put()线程的唤醒
Condition队列
-
Condition对象由RennLock中的newCondition()生成,实际上是AQS中的ConditionObject
-
条件队列实际上和阻塞队列都是使用Node表示队列结点
-
阻塞队列使用Node中的prev和next构成双向链表,条件队列使用Node中的nextWaiter构成单链表
-
当线程await()时,会进入条件队列阻塞,当有线程signal()时,被阻塞的线程会从条件队列迁移到阻塞队列,这时就可以等待头结点唤醒,抢锁成功后就可以继续运行了
final ConditionObject newCondition() {
return new ConditionObject();
}
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
//条件队列的第一个Node结点
private transient Node firstWaiter;
//条件队列的最后一个Node结点
private transient Node lastWaiter;
public ConditionObject() { }
解析await()流程
-
await()操作会释放掉持有的锁,主动进入条件队列挂起,什么时候会被唤醒?
-
情况一:其他线程调用了signal()或者signalAll(),迁移条件队列的结点到阻塞队列,在阻塞队列中被头结点唤醒去抢锁成功
-
情况二:转移至阻塞队列后,阻塞队列中的前驱结点状态是取消状态,马上唤醒当前结点
-
情况三:挂起期间,被中断唤醒,可能是在条件队列中被唤醒,也可能是阻塞队列,也可能是迁移过程中
public final void await() throws InterruptedException {
//判断当前线程的中断状态
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程包装成为Node结点然后加入到条件队列中,具体后面有写
Node node = addConditionWaiter();
//完全释放掉当前线程持有的锁,锁是可重入的,前面重入几次都要释放掉,具体后面会写
//savedState保存的是释放锁时的state值,当Node结点被迁移到阻塞队列抢锁时,再把state值设置成savedState
int savedState = fullyRelease(node);
//THROW_IE(-1):线程在condition队列挂起期间收到过中断信号
//0:线程在condition队列挂起期间没有收到过中断信号
//REINTERRUPT(1):线程在condition队列挂起期间没有收到过中断信号,在被迁移到阻塞队列时,或者之后收到过中断信号
int interruptMode = 0;
//isOnSyncQueue()true:当前Node结点已经被迁移到阻塞队列
// false:当前Node结点仍在条件队列中
while (!isOnSyncQueue(node)) {
//挂起当前线程
LockSupport.park(this);
//checkInterruptWhileWaiting():检查线程在挂起期间是否收到了中断信号
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//当前结点已经迁移到阻塞队列
//acquireQueued():竞争锁,具体见AQS源码阅读(一)
//acquireQueued(node, savedState) == true:线程在阻塞队列中被中断唤醒过
//interruptMode != THROW_IE :线程在condition队列挂起期间没有收到过中断信号
//REINTERRUPT:上面写过了
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//node.nextWaiter != null ? 何时出现这种情况
//如果node结点是被其他线程signal()操作迁移到阻塞队列会把nextWaiter置为空,具体后面会写
//如果node结点是被中断唤醒,通过transferAfterCancelledWait()操作把自己迁移到阻塞队列,此时nextWaiter != null
if (node.nextWaiter != null) // clean up if cancelled
//清理条件队列内取消状态的结点,具体后面有写
unlinkCancelledWaiters();
//挂起期间发生过中断 1.condition内的挂起 2.condition队列之外迁移到阻塞队列的挂起
if (interruptMode != 0)
//处理:具体后面会写
reportInterruptAfterWait(interruptMode);
}
//addConditionWaiter():将当前线程包装成为Node结点然后加入到条件队列中
private Node addConditionWaiter() {
//当前条件队列的最后一个结点
Node t = lastWaiter;
//t != null:当前条件队列中有结点
//t.waitStatus != Node.CONDITION:最后一个结点已经被取消
if (t != null && t.waitStatus != Node.CONDITION) {
//清理条件队列内取消状态的结点,具体后面有写
unlinkCancelledWaiters();
//更新t,上面可能会更新最后一个结点
t = lastWaiter;
}
//创建Node包装当前线程,设置状态为CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//条件队列中没有结点,当前结点是第一个入队的结点
if (t == null)
firstWaiter = node;
else
//条件队列已经有结点了,把当前结点放到最后面
t.nextWaiter = node;
//更新最后一个结点
lastWaiter = node;
//返回包装当前线程的Node结点
return node;
}
//fullyRelease():完全释放掉当前线程持有的锁,返回释放之前的state值
final int fullyRelease(Node node) {
//释放锁是否失败
boolean failed = true;
try {
//当前线程所持有的值
int savedState = getState();
//release():释放state,具体见AQS源码阅读(一)
if (release(savedState)) {
//失败标记设置为false
failed = false;
//返回当前线程释放的state值
//当Node结点被迁移到阻塞队列抢锁时,会再把state值设置成savedState
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//如果失败,把结点设置为取消状态
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
//isOnSyncQueue():判断当前结点是否迁移到阻塞队列
final boolean isOnSyncQueue(Node node) {
//结点被迁移到阻塞队列时会将结点的状态设置为0,再用enq()将结点加入阻塞队列,具体后面会写
//node.waitStatus == Node.CONDITION:当前结点为CONDITION状态,一定在条件队列中
//node.waitStatus == 0:当前结点状态被改为了0,要么是已经加入阻塞队列,要么马上加入阻塞队列
//node.waitStatus == 1:当前结点为取消状态
//什么时候会取消结点? fullyRelease()失败时
//node.prev == null:结点前驱为空,结点还未加入阻塞队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//enq()将node入队过程: 1.node.prev = tail 2.CAS操作node为tail 3 旧tail的next指向node
//从前面执行到这node.prev !=null,说明迁移第一步已经完成
//node.next != null:node已经有后继结点了,一定在阻塞队列中
if (node.next != null) // If has successor, it must be on queue
return true;
//迁移后两步可能完成了,从阻塞队列尾结点找当前结点node,如果有就返回true,没有返回false,具体就不写了
return findNodeFromTail(node);
}
//checkInterruptWhileWaiting():检查线程在挂起期间是否收到了中断信号
private int checkInterruptWhileWaiting(Node node) {
//线程是否收到了中断信号?
return Thread.interrupted() ?
//transferAfterCancelledWait():判断线程是在何时被唤醒的,具体后面会写
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}
//unlinkCancelledWaiters():清理条件队列内取消状态的结点
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为下个
firstWaiter = next;
else
//上一个正常结点后继指向当前结点的后继:中间取消状态结点出队
trail.nextWaiter = next;
//当前结点为最后一个
if (next == null)
lastWaiter = trail;
}
else
//当前结点是正常结点
trail = t;
t = next;
}
}
//transferAfterCancelledWait():1.线程在condition队列被中断唤醒,迁移到阻塞队列,返回true
2.线程被中断唤醒时不在condition队列,返回false
final boolean transferAfterCancelledWait(Node node) {
//CAS成功:当前结点还在condition队列中
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//加入到阻塞队列,具体见AQS源码阅读(一)
enq(node);
//true:线程是在条件队列内被中断的
return true;
}
//此时 情况一:当前结点已经被迁移到阻塞队列
// 情况二:当前结点正在被迁移到阻塞队列
//等一会,让结点到阻塞队列
while (!isOnSyncQueue(node))
Thread.yield();
//false:线程被中断时不在条件队列
return false;
}
//注意:线程收到中断信号时机不同,处理方式也不同
private void reportInterruptAfterWait(int interruptMode) throws InterruptedException {
//线程在condition队列中收到过中断信号
if (interruptMode == THROW_IE)
throw new InterruptedException();
//线程在condition队列之外收到过中断信号
else if (interruptMode == REINTERRUPT)
//给线程自己一个中断信号
selfInterrupt();
}
解析signal()流程
-
signal()会迁移condition队列第一个CONDITION状态结点到阻塞队列,让它在阻塞队列中等待被头结点唤醒去抢锁
-
signalAll()会迁移condition队列所有结点,迁移操作都是调用的transferForSignal()
public final void signal() {
//当前线程是否持有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取条件队列的第一个结点
Node first = firstWaiter;
//第一个节点不为空
if (first != null)
//迁移结点到阻塞队列,具体后面会写
doSignal(first);
}
private void doSignal(Node first) {
do {
//更新firstWaiter为当前结点的后一个结点
if ( (firstWaiter = first.nextWaiter) == null)
//后一个节点为空,更新lastWaiter
lastWaiter = null;
//断链
first.nextWaiter = null;
//transferForSignal():迁移first结点到阻塞队列,具体后面会写
//迁移失败就继续迁移,直到后面没有结点
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//CAS修改当前结点的状态
//true:当前结点在条件队列中正常状态
//false:1.当前结点在条件队列中取消状态
// 2.当前结点挂起期间,被中断信号唤醒,线程自己修改状态已经进入到阻塞队列
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//enq():把结点加入阻塞队列,具体见AQS源码阅读(一)
Node p = enq(node);
//p:当前结点在阻塞队列中的前驱结点
int ws = p.waitStatus;
//ws > 0:前驱结点的状态是取消状态
//ws <= 0: compareAndSetWaitStatus(p, ws, Node.SIGNAL):前驱结点状态更新为SIGNAL
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//前驱结点的状态是取消状态或者前驱结点状态更新失败,都会唤醒当前结点对应的线程
//此时该线程会在await()方法中醒来,上面写过了
LockSupport.unpark(node.thread);
return true;
}