juc之condition
前言
之前学synchronized的时候,我们肯定就学了Object类上面的wait 和 notify,notifyAll方法,这两个方法呢,是需要获取了对象锁才能进行使用的,因为如果线程都没有获取到资源,那么执行wait方法又还有什么意义呢?没获取锁,线程本身就在等待。
也由此可见,wait 和notify都要依赖于获取到了某个对象的锁才能进行操作。这也极大的限制了灵活性,而lock锁不会有这
个缺点,它可以构建多个等待队列
condition
我们看到Condition的这个接口,发现其有两个实现,一个是在aqs中 一个是在aqls中,分别有着实现
condition中定义了 7个接口方法,大概分为两类,一个是获取,一个是唤醒
condition这个类中,官方注释还给出了一个数组长度为100的有界缓冲的实现,利用condition进行实现的
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
下面我们aqs 对于condition的实现 是怎么实现的
Condition会在调用await()时将其包装成一个Node放入到条件队列中,但值得注意的是这个条件队列是基于单向链表实现,以nextWaiter来标记下一个节点的指针。
与同步队列恰好相反,条件队列并不会使用prev、next来串联链表,而是使用nextWaiter。
await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程加入到condition队列尾部,把waitStatus 不等于condition的结点移出链表外
Node node = addConditionWaiter();
// 同步队列首节点释放资源,唤醒下一个结点
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
// 简单来说,该方法的返回值代表当前线程是否在park的时候被中断唤醒,如果为true表示中断在signal调用之前,signal还未执行,那么这个时候会根据await的语义,在await时遇到中断需要抛出interruptedException,返回true就是告诉checkInterruptWhileWaiting返回THROW_IE(-1)。
如果返回false,否则表示signal已经执行过了,只需要重新响应中断即可
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);
}
看到addConditionWaiter()方法
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
// 把waitStatus 不等于condition的结点移出链表外
unlinkCancelledWaiters();
t = lastWaiter;
}
// 如果队列中还没有值,则把当前的线程置为第一个线程,如果有值,则往尾部进行添加
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
isOnSyncQueue(Node node)检查这个节点是不是在同步队列
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
// 同步队列 从尾部往上找 看是否在同步队列中
return findNodeFromTail(node);
}
signal()唤醒方法
看到了前面的await方法,如果线程被挂起了,那么唤醒的时候,应该做什么呢?可以应该把线程加入到同步队列中,但是应该也要判断一下waitstatus,
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal(Node first)
唤醒队列中的第一个节点
private void doSignal(Node first) {
//这个循环会进入两次
do {
//因为要释放了,所以下一个结点置为firstWaiter,如果为空,直接将lastWaiter置空,因为代表着之前队列中只有这一个结点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
// 因为要释放了 所以nextWaiter置空,方便gc
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
transferForSignal(first)
在transferForSignal方法中,我们先使用CAS操作将当前节点的waitStatus状态由CONDTION设为0,如果修改不成功,则说明该节点已经被CANCEL了,或者已经加入到了同步队列中了,则我们直接返回,操作下一个节点;如果修改成功,则说明我们已经将该节点从等待的条件队列中成功“唤醒”了,但此时该节点对应的线程并没有真正被唤醒,它还要和其他普通线程一样去争锁,因此它将被添加到sync queue的末尾等待获取锁。
我们这里通过enq方法将该节点添加进sync queue的末尾。关于该方法,我们在逐行分析AQS源码——独占锁的获取中已经详细讲过了,这里不再赘述。不过这里尤其注意的是,enq方法将node节点添加进队列时,返回的是node的前驱节点。
在将节点成功添加进sync queue中后,我们得到了该节点在sync queue中的前驱节点。我们前面说过,在sync queque中的节点都要靠前驱节点去唤醒,所以,这里我们要做的就是将前驱节点的waitStatus设为Node.SIGNAL, 这一点和shouldParkAfterFailedAcquire所做的工作类似:,
如果设置失败了,所不同的是,shouldParkAfterFailedAcquire将会向前查找,跳过那些被cancel的节点,然后将找到的第一个没有被cancel的节点的waitStatus设成SIGNAL,最后再挂起。而在transferForSignal中,当前Node所代表的线程本身就已经被挂起了,所以这里做的更像是一个复合操作——只要前驱节点处于被取消的状态或者无法将前驱节点的状态修成Node.SIGNAL,那我们就将Node所代表的线程唤醒,但这个条件并不意味着当前lock处于可获取的状态,有可能线程被唤醒了,但是锁还是被占有的状态,不过这样做至少是无害的,因为我们在线程被唤醒后还要去争锁,如果抢不到锁,则大不了再次被挂起。
值得注意的是,transferForSignal是有返回值的,但是我们在这个方法中并没有用到,它将在signal()方法中被使用。
final boolean transferForSignal(Node node) {
//去修改当前结点的状态,可能为false
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//如果修改当前结点的状态成功,则去请求加入到同步队列的尾部
Node p = enq(node);
int ws = p.waitStatus;
// 如果被取消了,或者是将
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
下面再来讲讲signalAll()方法,应该就是遍历的去进行释放,加入到同步队列的尾部,判断条件是nextWaiter() 为空
signalAll()
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
看代码就知道 和上面的分析一样
/**
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
再来看看condition中其他的await方法
await方法是支持中断的,看看
awaitUninterruptibly()
是不支持中断的,await方法支持中断 是有个checkInterruptWhileWaiting(node)和 reportInterruptAfterWait(interruptMode)方法去对中断进行处理。
再来看看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;
}
if (acquireQueued(node, savedState) || interrupted)
selfInterrupt();
}
long awaitNanos(long nanosTimeout)
加入了超时机制,如果超过了一定的时间,自动加入到同步队列中,且其支持中断
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)) {
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
对比await方法,差不多就多了这些代码
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
如果能够修改状态成功,说明还没有执行signal方法,就去加入到同步队列尾部,如果已经在队列中了就直接返回false,如果等待的时间小于1000纳秒,那么挂起也没啥意义,时间太短了。
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
awaitUntil(Date deadline)
还有个awaitUntil这个方法 和 awaitNanos是差不多的