参考 https://zhuanlan.zhihu.com/p/81017109 感谢原作者
=================================2020-11-24=============================
突然想到一个问题,那就是ConditionObject其实是在AQS里的,一个线程包成的Node要在同步队列和条件队列里移动,那就得保证是在同一个AQS的实现类里。
JAVA也确实是这么写的
ReentrantLock并没有直接实现AQS,而是通过内部类的方式
public Condition newCondition() { return sync.newCondition(); }
public void lock() { sync.lock(); }
还真的是同一个AQS
首先需要明确的是,Condition只工作在排他锁,一个排他锁可以有多个Condition,不过Condition的代码其实是在AQS里的
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;
顺便复习一下Node,
static final class Node { /** Marker to indicate a node is waiting in shared mode */ static final Node SHARED = new Node(); /** Marker to indicate a node is waiting in exclusive mode */ static final Node EXCLUSIVE = null; /** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3; volatile int waitStatus; volatile Node prev; volatile Node next; volatile Thread thread; Node nextWaiter;
上面需要注意的是在同步队列中的时候,是有前驱和后继的prev,next,但是在条件队列中只有nextWaiter,没有前驱,因为没必要
二 condtion.await()
public final void await() throws InterruptedException { //判断线程是否中断,如果线程处于中断状态是不能将线程加入condition列队中 if (Thread.interrupted()) throw new InterruptedException(); //将线程封装成node节点加入到condition列队中去 Node node = addConditionWaiter(); //释放当前线程拥有的锁资源调用 await 方法时, 当前线程是必须已经获取了独占的锁 long savedState = fullyRelease(node); int interruptMode = 0; //判断当前线程是否在 Sync Queue 里面(这里 Node 从 Condtion Queue 里面转移到 Sync Queue 里面有两种可能 // (1) 其他线程调用 signal 进行转移 // (2) 当前线程被中断而进行Node的转移(就在checkInterruptWhileWaiting里面进行转移)) while (!isOnSyncQueue(node)) { //将线程设置成block状态,此时节点处于阻塞状态不在继续向下执行,直到signal()方法唤醒该线程继续向下执行 LockSupport.park(this); //判断此次线程的唤醒是否因为线程被中断, 若是被中断, 则会在checkInterruptWhileWaiting的transferAfterCancelledWait //进行节点的转移; 返回值 interruptMode != 0 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) //// 说明此是通过线程中断的方式进行唤醒, 并且已经进行了 node 的转移, 转移到 Sync Queue 里面 break; } //调用 acquireQueued在 Sync Queue 里面进行 独占锁的获取, 返回值表明在获取的过程中有没有被中断过 //如果等于THROW_IE证明在等待过程被中断了 if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//即使是被中断的,condition的await也不会立刻返回还是得去竞争锁去,竞争到锁代码才能继续 interruptMode = REINTERRUPT; //通过 "node.nextWaiter != null" 判断 线程的唤醒是中断还是 signal, 因为通过中断唤醒的话, //此刻代表线程的 Node 在 Condition Queue 与 Sync Queue 里面都会存在 if (node.nextWaiter != null) // clean up if cancelled //清除 cancelled 节点 unlinkCancelledWaiters(); //"interruptMode != 0" 代表通过中断的方式唤醒线程 if (interruptMode != 0) //根据 interruptMode 的类型决定是抛出异常, 还是自己再中断一下 reportInterruptAfterWait(interruptMode); }
上面原作者的注释写的非常好,看了两遍就明白了好多。我再稍微总结下
1 执行await后,先加入条件队列,然后才是释放锁,由于新的线程会进行setHeader,所以执行await的线程就会从同步队列中被去掉
2 重点要分析中断代码,因为中断也会让park返回,此时把线程从条件队列转移到原同步队列
3 如果是正常通过signal结束park的话,会执行竞争锁逻辑,竞争不到阻塞,竞争到了方法才会返回
private int checkInterruptWhileWaiting(Node node) { //判断线程是否中断了,如果中断了调用transferAfterCancelledWait判断中断后是否被清除了 //线程被清除中断证明node节点处于清除状态后被列队清除掉了 //当线程等待超时或者被中断,则取消等待,设等待状态为-1,进入取消状态则不再变化 return Thread.interrupted() ? (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0; } final boolean transferAfterCancelledWait(Node node) { //通过cas设置node节点状态为0,预期值为condition,是否能设置成功 //signalled之前发生中断,因为signalled之后会将会将节点状态从CONDITION 设置为0 if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) { //如果设置成功调用enq方法将node加入到sync列队中去 enq(node); return true; } /* * If we lost out to a signal(), then we can't proceed * until it finishes its enq(). Cancelling during an * incomplete transfer is both rare and transient, so just * spin. */ //如果设置失败判断是否在sync列队中,如果没在列队中则将当前线程谦让出去返回false // signalled之后发生中断,这个分支的意思就是signal之后才发生的中断,这种算是半正常情况 // 如果节点还没有被放入同步队列,则放弃当前CPU资源 // 让其他任务执行,因为此时线程已经中断了而且还没有在sync列队中那么就让出当前cpu资源 while (!isOnSyncQueue(node)) Thread.yield(); return false; }
transferAfterCancelledWait 如果返回是false的话,中断类型就是 REINTERRUPT,这种中断类型不需要把异常往上抛
有趣的地方:能够看到await在一开始的地方并没有检查当前线程是否正持有锁,而是直到执行到fullyRelease才会触发报错,那么问题来了,当前线程已经包成了Node加入到了条件队列了。
如果线程死掉那这个Node怎么办。李老爷子哪能想不到呢,finally里面 node.waitStatus = Node.CANCELLED; 可以保证会在之后该节点清理掉
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; } }
三 signal
public final void signal() { //isHeldExclusively判断当前线程是否获取到锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) //唤醒等待列队中的第一个线程 doSignal(first); }
private void doSignal(Node first) { do { if ( (firstWaiter = first.nextWaiter) == null)//这种情况说明first后面就没有节点了,同时firstWaiter已经指向了first的next lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); }
先把Node从队列中去掉,也就是 first.nextWaiter = null; 断开条件队列
final boolean transferForSignal(Node node) { /* * If cannot change waitStatus, the node has been cancelled. */ if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; /* * Splice onto queue and try to set waitStatus of predecessor to * indicate that thread is (probably) waiting. If cancelled or * attempt to set waitStatus fails, wake up to resync (in which * case the waitStatus can be transiently and harmlessly wrong). */ Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))//enq的返回值是上一次的tail,这句话的意思是如果tail是取消,或者CAS SIGNAL失败,就唤醒node的线程一次,
//这是防止可能不会唤醒,其实正常流程只需要把节点从条件队列里摘除,同时放入同步队列就可以了 LockSupport.unpark(node.thread); return true; }
代码量中的不大,英文的注释也说的很明白。先把Node入同步队列,然后要把node前面的节点CAS成SIGNAL,
正常的流程到这里就可以了。
如果失败了就唤醒node的线程一次,再次获取锁的时候,在把自己park之前会检查自己的前驱节点的waitStatus,并且把所有已经取消的节点中队列中去掉
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus; if (ws == Node.SIGNAL) /* * This node has already set status asking a release * to signal it, so it can safely park. */ return true; if (ws > 0) { /* * Predecessor was cancelled. Skip over predecessors and * indicate retry. */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { /* * waitStatus must be 0 or PROPAGATE. Indicate that we * need a signal, but don't park yet. Caller will need to * retry to make sure it cannot acquire before parking. */ compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
四 总结
await:先把当前线程包成Node加入到条件队列,然后释放锁,这样自然就从同步队列中被去掉了,最后把自己阻塞住。
这里会检查中断,如果线程是由于中断被唤醒,就把线程再次加入到同步队列中,但是此时线程已经是取消状态,并且会抛出异常。
signal:把队头节点从队头去掉,CAS改变waitStatus为0,把节点加入到同步队列中,正常情况就到此结束了。如果node加入后它的前驱是已取消状态或者CAS成SIGNAL失败,就唤醒一次