java.util.concurrent最初的一个目标,就是消除之前java使用synchronized与Object的各种监视器方法(notify(),wait())等等来实现线程之前的各种同步与通信的不变与低效,我们现在知道,j.u.c中实现了AQS,并通过AQS实现了一些锁比如我们讲到的ReentrantLock。
Lock对象的lock()方法就相当于synchronized,但J.U.C的Lock对象当然不能和notify(),wait()等监视器方法直接相互作用,J.U.C必须要有一套自己的机制来实现这些监视器方法,这套机制通过Condition类来实现。
我们先来看看Condition定义的一些通用接口:
public interface Condition
{
//当前线程进入等待状态,看到这个throw就应该知道是相应中断的
public abstract void await()
throws InterruptedException;
//线程进入等待状态,并不相应中断
public abstract void awaitUninterruptibly();
//线程进入等待状态,超时则报出被中断
public abstract long awaitNanos(long l)
throws InterruptedException;
//线程将一直等待直到线程被中断,或超时、或被signal()
public abstract boolean await(long l, TimeUnit timeunit)
throws InterruptedException;
//线程等待的deadline形式,一直等待,等到到date那个时候,或者中途被中断
public abstract boolean awaitUntil(Date date)
throws InterruptedException;
//类似notify()唤醒某一个被当前Condition对象的Lock对象锁住的线程
public abstract void signal();
//类似notifyAll()唤醒所有被当前Condition对象的Lock对象锁住的线程
public abstract void signalAll();
}
看起来都非常舒服,和Object类的监视器方法对应的很好。
从这些接口中我们要知道推断出一个背景,就是每一个Condition对象都需要和一个Lock对象关联起来。比如下面的代码
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
System.out.println("等我10秒钟");
long before = System.currentTimeMillis();
condition.await(10, TimeUnit.SECONDS);
long after = System.currentTimeMillis();
long time = ((after - before) / 1000);
System.out.println("等了" + time + "s");
输出:
等我10秒钟
等了10s
可以看到这个condition一定要lock.newCondition()这种方式获取,事实上也只能通过这种方式获取,因此J.U.C将Condition作为AQS的内部类来实现。
下面我们来看看condition中最典型的两个方法,await()和signal(),顺带提一下signalAll().
对于await方法来说,我们通过对AQS的了解,大致也可以猜到Condition也是通过一个队列来处理被await()的线程:
private transient Node firstWaiter;
private transient Node lastWaiter;
这个队列也是以AQS中定义的Node作为节点,需要注意的是,Condition的所有代码只用到了Node的nextWaiter并没有用到prev和next,这个一是为了万一有Node同时被AQS和Condition使用,能够防止Node的指针同时被Condition和AQS本身操作,即隔离Condition和AQS的操作;二是由于只定义了一个nextWaiter,并没有定义什么preWaiter,实现的是一个单向链表的队列,AQS的CLH队列则是双向链表实现的。
下面是await()方法的实现:
public final void await()
throws InterruptedException
{
//判断线程是否中断
if(Thread.interrupted())
throw new InterruptedException();
//将线程加入Condition的等待队列
Node node = addConditionWaiter();
//释放condition,唤醒condition等待队列中的下一个节点
long l = fullyRelease(node);
int i = 0;
do
{
//检查线程是否在AQS的CLH队列中,如果在,
//则参与锁的竞争,不用将线程挂起
if(isOnSyncQueue(node))
break;
//线程被Condition挂起
LockSupport.park(this);
} while((i = checkInterruptWhileWaiting(node)) == 0);
//如果参与锁的竞争,尝试自旋地获取锁
if(acquireQueued(node, l) && i != -1)
i = 1;
//剔除所有被Cancelled线程
if(node.nextWaiter != null)
unlinkCancelledWaiters();
//根据被中断的状态来处理线程的中断
if(i != 0)
reportInterruptAfterWait(i);
}
这里比较难理解的就是checkInterruptWhileWaiting(node)这个方法,这个方法是这样写的:
/**
* Checks for interrupt, returning THROW_IE if interrupted
* before signalled, REINTERRUPT if after signalled, or
* 0 if not interrupted.
*/
private int checkInterruptWhileWaiting(Node node) {
return (Thread.interrupted()) ?
((transferAfterCancelledWait(node))? THROW_IE : REINTERRUPT):0;
}
从注释可以看到,在线程中断的情况下,如果这个线程在被signal前中断,返回THROW_IE ,如果在被signal后中断,返回REINTERRUPT,未被中断则返回0。
(transferAfterCancelledWait(node)这个方法主要是用于在已经判断线程为中断的情况下,判断线程的中断是在signal前还是signal后,我们来看看具体是怎么实现的。
/**
* Transfers node, if necessary, to sync queue after a cancelled
* wait. Returns true if thread was cancelled before being
* signalled.
* @param current the waiting thread
* @param node its node
* @return true if cancelled before the node was signalled.
*/
final boolean transferAfterCancelledWait(Node node) {
//当Node的状态为Contion,尝试将Node的状态设置为0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//设置成功后进入AQS同步队列
enq(node);
//表示该线程在signal前就被interrupt了
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.
*/
//如果线程不在AQS同步队列中
while (!isOnSyncQueue(node))
//线程让步
Thread.yield();
//表示线程在被signal后才被interrupt
return false;
}
这段代码有点高能,有点像高数里面那些‘显然’、‘易证得’,我们来研究一下,其实写这篇博客的时候我也不是很懂,一起学习,一起学习。
首先我们要知道这个线程已经是被中断了,compareAndSetWaitStatus(node, Node.CONDITION, 0)这一步,我想应该是做一个判断,由于signal操作时,也会将node 的status设置为0,所以如果此时能够对status进行期望值为CONDITION的CAS操作,那肯定是没有发生signal的,而我已经判断到了线程已经中断了,所以线程中断发生在signal之前,返回true。如果CAS操作失败呢,那我们只能说,不一定,我们能知道的只是signal已经发生,中断也已经发生。signal发生了,那一定有一刻,他会进入AQS的同步队列去竞争锁,这一点在后面讲signal时也能看到,所以就自旋地检查自己是否在AQS同步队列中,如果不在,让步给其他线程执行。
await后面的一句reportInterruptAfterWait(i)就是根据transferAfterCancelledWait(node)的返回值来处理线程的中断,我们可以来看看
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
/**
* Convenience method to interrupt current thread
*/
private static void selfInterrupt() {
Thread.currentThread().interrupt();
}
如果transferAfterCancelledWait(node)返回了THROW_IE,直接抛出异常,如果返回REINTERRUPT,则后续还需要对当前线程调用一次interrupt(),为什么这里需要再调用一次呢?这是因为我们await最初做了一个判断:
//判断线程是否中断
if(Thread.interrupted())
这个interrupted()方法,不仅能够判断线程是否中断,还会将线程的中断标志清除,我们的REINTERRUPT就是为了补上这个标志。
下面我们来看signal()方法
/**
* Moves the longest-waiting thread, if one exists, from the
* wait queue for this condition to the wait queue for the
* owning lock.
*
* @throws IllegalMonitorStateException if {@link #isHeldExclusively}
* returns {@code false}
*/
public final void signal() {
//判断当前线程是否拥有锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//对Condition等待队列中的第一个节点进行signal操作
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
/**
* Removes and transfers nodes until hit non-cancelled one or
* null. Split out from signal in part to encourage compilers
* to inline the case of no waiters.
* @param first (non-null) the first node on condition queue
*/
private void doSignal(Node first) {
do {
//当前节点的后继节点为null
if ( (firstWaiter = first.nextWaiter) == null)
//队列置空
lastWaiter = null;
//当前节点从队列中脱离出来
first.nextWaiter = null;
}
//处理signal的一些状态转换
while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
/**
* Transfers a node from a condition queue onto sync queue.
* Returns true if successful.
* @param node the node
* @return true if successfully transferred (else the node was
* cancelled before signal).
*/
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//对node的状态进行CAS操作,如果他不是CONDITION状态,
//则一定是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).
*/
//线程进入AQS同步队列竞争锁状态,返回前驱
Node p = enq(node);
//检查前驱状态,如果前驱状态无法被设置为signal,
//或已被cancelled或已占有锁
int c = p.waitStatus;
//如果发现前驱节点等待状态无法置为signal
if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
//立即唤醒当前节点,从之前的await中的循环中unpark出来,
//并执行await接下来的acquireQueued操作,即获取锁操作
//这个操作不管node在队列中是否为头部,直接参与锁的竞争
LockSupport.unpark(node.thread);
//否则将node放在队列中,等待成功获取锁的头结点来唤醒,即在AQS同步队列中
//acquire操作的时候,也即,Lock对象的unlock操作的时候,唤醒这个节点
return true;
}
我们要理解一点,就是如果一个node进入了AQS的等待队列,他迟早要被unpark或interrupt,因此,signal操作只需要将node放入AQS队列让它去争抢锁状态就可以了,但如果它在AQS同步队列中的前驱已被cancelled或者无法唤醒,那就立刻unpark,从await中的while循环中出来,进行锁的争抢,至于为什么要这样,也许是因为性能的原因吧。