属性分析
在Condition里面有一个Condition Queue,这个Condition Queue是一个单链表,是通过Node得nextWaiter来关联起来得。下面得就是头和尾节点,Node是和WaitQueue总得节点类型是一致得。
private transient Node firstWaiter;
private transient Node lastWaiter;
// 下面两个是中断模式。
/** Mode meaning to reinterrupt on exit from wait */
private static final int REINTERRUPT = 1;
/** Mode meaning to throw InterruptedException on exit from wait */
private static final int THROW_IE = -1;
构造方法分析
没有什么可说得,一个无参构造。
public ConditionObject() { }
常用方法分析
signal
做唤醒操作
唤醒一个,将Condition queue里面的头节点转移到WaitQueue中
public final void signal() {
// 判断当前持有错的线程是否是当前线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 拿到condition queue中的firstWaiter
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal
从Condition queue中转移,从头结点开始,将一个节点转移到Wait Queue中,如果在这个期间,当前节点的状态发生了变化,导致节点的类型不是CONDITION,Condition queue中指针后移。
private void doSignal(Node first) {
do {
// 头结点指针后移,如果头结点变为null了,那就说明队列已经空了,所以lastWaiter也是null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 清楚引用关系
first.nextWaiter = null;
// 将first从condition queue中转移到waitQueue中去。
// 如果没有移动成功,将头结点赋值first,要注意,这个时候firstwaiter已经后移了。
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
AQS#transferForSignal
将node从condition queue转移到WaitQueue中
先修改Node的waitStatus,将waitStatus从CONDITION变为0,因为插入到WaitQueue中的节点的初始状态肯定是0。
在通过AQS的enq方法,将节点添加到 waitQueue中,enq方法返回的是当前节点插入到waitQueue中的前驱节点。
后面就会改变前驱节点的waitStatus。如果前继状态为CANCELLED,或者改变前驱节点的时候失败,直接唤醒当前节点这的线程。
final boolean transferForSignal(Node node) {
// 改变状态,将CONDITION变为0,如果之前不是CONDITION,直接返回false。
// 一般来说,调用这个方法得node都是CONDITION,将CONDITION变为0,是为了之后得入waitQueue得操作,
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//入队,enq方法会将这个node入队到WaitQueue中得。
// 这个方法会返回当前插入节点得前驱节点。
// 在waitQueue没有初始化得时候,这里得node就是header节点。会将Condition queue放在waitQueue中,因为放在waitQueue
// 中得节点,初始得WaitStatus是0,所以,才有上面修改为0得操作。
Node p = enq(node);
//拿到头节点得waitStatus
int ws = p.waitStatus;
// ws > 0 : 节点得状态是取消
// !compareAndSetWaitStatus(p, ws, Node.SIGNAL) :将头节点得状态变为SIGNAL,
// 如果前驱节点是取消状态,或者将前驱节点得state变为 Node.SIGNAL,失败,就唤醒当前节点得线程,
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 进入到这里,说明直接唤醒节点中park得线程,
// 这个操作说明,头节点已经有唤醒后继节点得任务,或者说,在cas得时候有别的线程已经设置了状态,导致firstWaiter 已经状态已经变了
LockSupport.unpark(node.thread);
return true;
}
signalAll
唤醒所有
将Condition Queue中得所有节点移动到 WaitQueue中
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
doSignalAll
唤醒所有得节点
将Condition queue中所有的节点转移到 WaitQueue中。
transferForSignal方法在上面已经分析过了
private void doSignalAll(Node first) {
// 因为要做唤醒所有得操作,所以,先将链表得头尾节点变为null
lastWaiter = firstWaiter = null;
// 然后开始遍历循环。一般来说,first都是头节点
do {
// 拿到下一个节点
Node next = first.nextWaiter;
// 清楚引用关系
first.nextWaiter = null;
//
transferForSignal(first);
first = next;
} while (first != null);
}
await
等待
实现等待的操作大体有下面的几个步骤
- 构建Condition Queue。
- 释放锁。
- park线程,等待唤醒。
- 唤醒之后,如果有中断就处理中断,否则一切正常。
这是主要得一个方法,这里得代码逻辑要和signal结合在一起看,比较好理解,为了方便理解,画一个图看看。
上面是下面代码的图像化,接下来继续说说while循环是怎么和signal结合起来的。
await和signal是怎么结合的?
-
首先要知道,有两个队列,一个是waitQueue(用作锁),一个是Condition Queue(用作Condition),不同的Condition是不同的Condition Queue。
-
await操作会构建节点,添加到Condition Queue中,在没有signal之前,节点是不会到waitQueue中的。所以,while循环里面的isOnSyncQueue在signal之前,他的状态是CONDITION,并且它也没有在Wait Queue中,所以他一直返回的false。
-
在signal的时候,不管是signal还是signalAll(这俩只不过是一次和多次的差别),都会将node从Condition Queue中解绑,并且添加到Wait Queue中去。并且还会将当前节点的WaitStatus变为0,将当前节点在WaitQueue中的前驱节点的状态变为-1。(不包含取消节点)。
-
在上面的操作之后,当前节点已经到 WaitQueue中了,就走正常的锁的一套了,如果前驱节点释放锁之后,唤醒了当前节点,当前节点就会在while里面醒来,当然这里还少了一步判断park期间有没有中断。while条件不满足,就继续走下面的代码,(获取锁,判断中断模式)
要知道 park是可以响应中断的,并且中断是不会报错的,只是改了中断标志。
public final void await() throws InterruptedException {
// 判断当前线程是否中断,如果中断,直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 构建ConditionQueue节点
Node node = addConditionWaiter();
// 释放当前持有得锁
int savedState = fullyRelease(node);
// 中断模式默认
// 这个用于判断中断,不同得中断有不同得模式
int interruptMode = 0;
// 通过这个条件判断,是有signal操作,
// 如果唤醒,这里得判断就不会进去了,
// 因为当前得节点已经在waitQueue中了,
// 这里的这个要和signal结合起来,signalAll会将Condition Queue里面的节点全部移动到WaitQueue中,并且会将
// 节点的waitStatus先改为0,在把节点的前驱节点变为-1.
// 所以,在没有调用ignal之前,当前节点肯定是没有在waitQUeue中的。
while (!isOnSyncQueue(node)) {
// 如果没有,就park当前线程
LockSupport.park(this);
// 之前说过,pakr得线程是可以响应中断得,
// 要是一切正常得话,就不会走到这里,上面得while循环会退出。
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 重新获取锁 并且interruptMode != THROW_IE
// acquireQueued 返回值 true。 表示当前线程在获取锁得时候中断了。
// 返回false,说明当前线程没有中断。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 如果获取锁得时候再次中断了,并且interruptMode不是THROW_IE,重新赋值为REINTERRUPT。
interruptMode = REINTERRUPT;
// 如果在conditionQueue中,当前节点还有nextWaiter,
if (node.nextWaiter != null)
// 如果没有
unlinkCancelledWaiters();
// 通过中断模式来做操作,
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
addConditionWaiter
添加一个新得waiter到wait Queue中
private Node addConditionWaiter() {
// 尾节点
Node t = lastWaiter;
// 如果lastWaiter不是null并且lastWaiter的waitStatus不是CONDITION,
if (t != null && t.waitStatus != Node.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;
}
unlinkCancelledWaiters
这个我不是很理解,不太懂这个是干嘛。为什么要在addWaiter和 node.nextWaiter != null,是什么样的情形下面会出现这样操作 todo 之后补充
private void unlinkCancelledWaiters() {
// 头节点
Node t = firstWaiter;
Node trail = null;
// 开始循环
while (t != null) {
Node next = t.nextWaiter;
// t(在第一次循环得时候,就是头节点) 不是 condition得时候
if (t.waitStatus != Node.CONDITION) {
// nextwaiter变为null,消除引用关系
t.nextWaiter = null;
if (trail == null)
// 修改头节点为t得next(第一次循环得时候就是头节点得下一个节点)
firstWaiter = next;
else
// trail连接起来
trail.nextWaiter = next;
if (next == null)
// 如果next为空,将trail复制给lastWaiter
lastWaiter = trail;
}
else
// trail为t(第一次为头节点)
trail = t;
// 指针后移
t = next;
}
}
AQS#fullyRelease
调用AQS中得release方法,将当前锁得state作为参数传递进去。并且返回state,否则就报错,并且这个节点得waitStatus变为CANCELLED。
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
// 这方法是release得,总之就是释放锁,如果释放成功了,就直接返回原来得state
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
// 如果失败了,就会将当前节点得waitStatus变为Node.CANCELLED
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
AQS#isOnSyncQueue
判断当前得节点是否在同步队列里面(WaitQueue中)
node的状态是CONDITION,或者node没有前驱(注意,这里说的前驱是在WaitQueue)。就说明没有在waitQUeue中。
如果节点有后继,说明他已经在waitQueue中了
如果说有前驱节点但是没有后继节点,说明它是尾节点,所以,下一步就开始从尾节点开始,遍历判断了
final boolean isOnSyncQueue(Node node) {
// 如果waitStatus是CONDITION 或者没有前驱(说明肯定没有到waitQueue中)
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果有后继节点,他肯定是已经在队列里面了
if (node.next != null)
return true;
// 如果他不在waitQueue队列里面,或者在waitQueue得尾节点, waitStatus不是CONDITION就会走到这里,从后往前搜。
return findNodeFromTail(node);
}
AQS#findNodeFromTail
从后往前搜索,如果节点在一个同步得queue中,只有isOnSyncQueue.方法才可以调用他。
private boolean findNodeFromTail(Node node) {
// 从后往前面搜
Node t = tail;
for (;;) {
// 死循环
// 找到了
if (t == node)
return true;
// 找完了
if (t == null)
return false;
// 指针前移
t = t.prev;
}
}
checkInterruptWhileWaiting
检查当前得节点在park得时候,有没有被中断过。如果没有中断过,就返回0,否则就到transferAfterCancelledWait
里面判断。
如果中断在signalled之前,返回THROW_IE,在signalled之后,返回REINTERRUPT。
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
AQS#transferAfterCancelledWait
判断当前被中断得线程中断得时候是在唤醒 (signalAll或者signal) 操作之前还是之后
在signal之前得节点,他得waitStatus肯定是CONDITION,如果是CONDITION,就会将waitStatus变为0,重新入队。
在signal之后得节点,他得waitStatus肯定不是CONDITION,会在while循环里面继续判断当前得节点是否在waitQueue中,如果不存在,当前得线程让出cpu使用权。等待signal。等signal之后,再返回。这就是 中断在唤醒(signalAll或者signal)之后。
final boolean transferAfterCancelledWait(Node node) {
// 再signal之前,node得状态肯定是CONDITION,如果不是condition,就说明在signal之后。
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//重新入队
enq(node);
return true;
}
// while 判断当前得节点是否在队列中,如果没有在队列中,就循环让出cpu使用权,等待一下下
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
reportInterruptAfterWait
通过前面确认得中断模式来判断,按照中断模式来抛出异常或者调用当前线程得中断方法。
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
awaitNanos
带超时时间的await
基本的功能是和await一样,和ReentrantLock的tryLock(带超时时间)的实现是一样的,在await方法上面,添加了超时时间。
典型的超时时间的写法是,先用当前实现+超时时间=到期时间。然后再唤醒之后用到期时间-当前时间,如果小于0,说明已经超时了。
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
// 到期时间
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 如果小于等于0,说明awaitNanos不需要等待,直接将当前节点添加到waitQueue中,重新获取锁
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
// 还是spinForTimeoutThreshold,熟悉的感觉,
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 检查中断
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
// 这里就和await的一样了
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
// 返回剩余时间
return deadline - System.nanoTime();
}
awaitUntil
await(long time, TimeUnit unit)
这几个超时时间的await,本体都和await的一样了。无非都是添加超时时间。
awaitUninterruptibly
不中断,不间断的await
本地还是await,不过没有中断模式的判断了。只要有中断发生(不管是在Condition queue还是wait Queue),都会调用当前线程的interrupt方法。
public final void awaitUninterruptibly() {
// 构建节点
Node node = addConditionWaiter();
// 释放锁,并且将原来得state返回
int savedState = fullyRelease(node);
//
boolean interrupted = false;
while (!isOnSyncQueue(node)) {
// 这里就直接唤醒了,肯定是唤醒之后,上面得条件不满足,然后重新获取锁
LockSupport.park(this);
if (Thread.interrupted())
interrupted = true;
}
// 如果获取锁的时候发生了中断,获取当前线程在park的时候发生了中断,就调用自己的中断方法。
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
hasWaiters
判断condition上是否还有waiter
public boolean hasWaiters(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
// 这是调用的同步器的,直接看
return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
AQS#hasWaiters
public final boolean hasWaiters(ConditionObject condition) {
if (!owns(condition))
throw new IllegalArgumentException("Not owner");
return condition.hasWaiters();
}
ConditionObject#hasWaiters
循环遍历 condition queue,判断节点的状态是否有一个是CONDITION
protected final boolean hasWaiters() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
if (w.waitStatus == Node.CONDITION)
return true;
}
return false;
}
getWaitQueueLength
判断当前的Condition里 Condition queue的数量,这里的代码逻辑很简答了,就不在这里列出来了,本质就是遍历Condition queue。
getWaitingThreads
判断当前的Condition里等待的线程的数量,这里的代码逻辑很简答了,就不在这里列出来了,本质就是遍历Condition queue,判断CONDITION。
关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢