Condition
在ReentrantLock中我们通过Condition来实现指定线程的唤醒
在CyclicBarrier底层我们也是通过ReentrantLock和Condition实现的
我们知道一个ReentrantLock除了维护一个CLH同步队列,同时它还维持一个Condition条件队列(好像每一个Condition对象都维持着这样一个队列)。这个条件队列中Node和CLH中Node是同一种结构,像之前节点的类型Condition就是专门放在这个队列里面的类型。
Condition队列的数据结构:
public class ConditionObject implements Condition, java.io.Serializable { private static final long serialVersionUID = 1173984872572414699L; //头节点 private transient Node firstWaiter; //尾节点 private transient Node lastWaiter; public ConditionObject() { } }
从上面代码可以看出Condition队列拥有首节点(firstWaiter),尾节点(lastWaiter)。当前线程调用await()方法,将会以当前线程构造成一个节点(Node),并将节点加入到该队列的尾部。

等待:
调用Condition的await()方法会使当前线程进入等待状态,同时会加入到Condition等待队列同时释放锁。当从await()方法返回时,当前线程一定是获取了Condition相关连的锁。
public final void await() throws InterruptedException { // 当前线程中断 if (Thread.interrupted()) throw new InterruptedException(); //当前线程加入等待队列 Node node = addConditionWaiter(); //释放锁 long savedState = fullyRelease(node); int interruptMode = 0; /** * 检测此节点的线程是否在同步队上,如果不在,则说明该线程还不具备竞争锁的资格,则继续等待 * 直到检测到此节点在同步队列上 */ 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); }
首先将当前线程新建一个节点同时加入到条件队列中,
然后释放当前线程持有的同步状态。
然后则是不断检测该节点代表的线程释放出现在CLH同步队列中(收到signal信号之后就会在AQS队列中检测到),如果不存在则一直挂起,否则参与竞争同步状态。
加入条件队列:
该方法主要是将当前线程加入到Condition条件队列中。当然在加入到尾节点之前会清楚所有状态不为Condition的节点。(在移步到CLH队列的时候状态会改变)
private Node addConditionWaiter() { Node t = lastWaiter; //尾节点 //Node的节点状态如果不为CONDITION,则表示该节点不处于等待状态,需要清除节点 if (t != null && t.waitStatus != Node.CONDITION) { //清除条件队列中所有状态不为Condition的节点 unlinkCancelledWaiters(); t = lastWaiter; } //当前线程新建节点,状态CONDITION Node node = new Node(Thread.currentThread(), Node.CONDITION); /** * 将该节点加入到条件队列中最后一个位置 */ if (t == null) firstWaiter = node; else t.nextWaiter = node; lastWaiter = node; return node; }
fullyRelease(Node node),负责释放该线程持有的锁。
final long fullyRelease(Node node) { boolean failed = true; try { //节点状态--其实就是持有锁的数量 long savedState = getState(); //释放锁 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) node.waitStatus = Node.CANCELLED; } }
isOnSyncQueue(Node node):如果一个节点刚开始在条件队列上,现在在同步队列上获取锁则返回true
final boolean isOnSyncQueue(Node node) { //状态为Condition,获取前驱节点为null,返回false if (node.waitStatus == Node.CONDITION || node.prev == null) return false; //后继节点不为null,肯定在CLH同步队列中 if (node.next != null) return true; return findNodeFromTail(node); }
unlinkCancelledWaiters():负责将条件队列中状态不为Condition的节点删除
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; } }
通知:
调用Condition的signal()方法,将会唤醒在等待队列中等待最长时间的节点(条件队列里的首节点),在唤醒节点前,会将节点移到CLH同步队列中。
该方法首先会判断当前线程是否已经获得了锁(执行signal这个方法的线程必须获取到锁?),这是前置条件。然后唤醒条件队列中的头节点。(这个是否可以无视CLH队列的顺序呢?直接去尝试获取锁吗?:不能无视顺序,还是得按照原来的方式)
public final void signal() { //检测当前线程是否拥有锁 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); //头节点,唤醒条件队列中的第一个节点 Node first = firstWaiter; if (first != null) doSignal(first); //唤醒 }
doSignal(Node first):唤醒头节点
doSignal(Node first)主要是做两件事:1.修改头节点,2.调用transferForSignal(Node first) 方法将节点移动到CLH同步队列中。
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) { //将该节点从状态CONDITION改变为初始状态0, if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; //将节点加入到syn队列中去,返回的是syn队列中node节点前面的一个节点 Node p = enq(node); int ws = p.waitStatus; //如果结点p的状态为cancel 或者修改waitStatus失败,则直接唤醒 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }
总结:
一个线程获取锁后,通过调用Condition的await()方法,会将当前线程先加入到条件队列中,然后释放锁,
最后通过isOnSyncQueue(Node node)方法不断自检看节点是否已经在CLH同步队列了,进入while循环,park在那里,等待signal,将这个节点移到同步队列,等待唤醒。才会跳出这个whle循环,然后尝试去获取同步状态(不一定争夺成功)
如果是则尝试获取锁,否则一直挂起。
当线程调用signal()方法后,程序首先检查当前线程是否获取了锁,然后通过doSignal(Node first)方法唤醒条件队列的首节点。被唤醒的线程,将从await()方法中的while循环中退出来,然后调用acquireQueued()方法竞争同步状态。
本文来自博客园,作者:LeeJuly,转载请注明原文链接:https://www.cnblogs.com/peterleee/p/10402630.html

浙公网安备 33010602011771号