Condition
Condition用途
当多个线程需要访问一个共享资源时,需要给共享资源加锁。 当一个线程释放锁时,所有等待锁的线程都会尝试去获取锁。如果想只让部分等待锁的线程去获取锁时,就需要用到Condition。
执行wait方法后,线程会阻塞,并释放同步代码块的锁(sleep方法会持有锁),notify的方法执行,会唤醒某个线程,但是如果有多个线程执行wait方法阻塞,notify的执行只会唤醒其中某个线程,并不能指定线程唤醒,这时要使用notifyAll才能达到唤醒所有阻塞线程。这样确实有点麻烦,而condition的引入就是为了解决只唤醒执行阻塞的线程。它具有超时作用,即超过某段时间,即会自动唤醒,不会造成一直阻塞。常用的阻塞队列就是这个类实现的。
使用注意点,await和signal必须要在lock方法后执行,如果不执行lock方法就执行await或signal,会出现异常的。
head、tail是AQS的同步队列,firstWaiter、lastWaiter是condition的条件队列。
为了方便理解,本文中的同步队列指AQS的队列,条件队列指condition的队列。
整体分析
Condition具体实现在AbstractQueuedSynchronizer类中。这个类中管理了一个同步队列和N多个条件队列。
阻塞队列记录了等待获取锁的线程,头结点记录了当前正在运行的线程。
条件队列记录了由Condition.await()阻塞的线程,一个Lock可以有多个Condition,每个Condition是一个队列。
Condition是AbstractQueuedSynchronizer的一个内部类ConditionObject,所以创建的Condition对象是可以访问整个AbstractQueuedSynchronizer对象的属性的,通过这样将Condition与Lock相关联。
Condition原理介绍
Condition是AQS的内部类。每个Condition对象都包含一个条件队列。
等待队列是一个FIFO的队列,在队列中的每个节点Node都包含了一个线程引用,该线程就是在Condition对象上等待的线程。
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; /** * Creates a new {@code ConditionObject} instance. */ public ConditionObject() { } }
条件队列的基本结构如下所示:
等待队列分为首节点和尾节点。
当一个线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入条件队列。新增节点就是将尾部节点指向新增的节点。节点引用更新本来就是在获取锁以后的操作,所以不需要CAS保证。同时也是线程安全的操作。
当一个线程调用signal()将条件队列中的第一个节点加入到同步队列的尾端,表示可以被唤醒执行。
await()
如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入条件队列并进入等待状态。
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); // 将当前线程包装成一个Node节点,并节点状态为condition,值为-2 Node node = addConditionWaiter(); // 释放当前线程持有的所有锁 long savedState = fullyRelease(node); int interruptMode = 0; // 判断当前线程是否在lock的等待队列中,即在head->tail队列中,如果不在,那就是还在condition的等待队列中,阻塞当前线程。 while (!isOnSyncQueue(node)) {
// 当前condition被执行了signal方法后,被unpark唤醒 LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } // 将当前线程加入同步队列中,获取Lock的锁 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); } final long fullyRelease(Node node) { boolean failed = true; try { // 获取当前线程状态值,即持有了几个锁 long savedState = getState(); // 释放锁,最后执行到ReentrantLock的tryRelease()方法,该段代码会判断当前线程是与持有锁的线程是同一个线程,如果不是,则抛异常 if (release(savedState)) { failed = false; return savedState; } else { throw new IllegalMonitorStateException(); } } finally { if (failed) // 抛异常了,则将此节点状态改为canceled,等待从队列中移除 node.waitStatus = Node.CANCELLED; } } public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
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;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
*/
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
signal()
signal起的作用是通过LockSupport.unpark(node.thread);直接唤醒条件队列里等待的线程,await中通过acquireQueued将当前线程加入同步队列中,获取Lock的锁。
public final void signal() { // 与await方法一样,如果不在lock方法内执行,则也会抛异常 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) //唤醒condition等待队列中线程 doSignal(first); } 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) { /* * If cannot change waitStatus, the node has been cancelled. 如果改变node节点状态失败,即该节点被取消了 */ if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; // 将该节点加入ReentrantLock等待队列中,即head->tail队列中 Node p = enq(node); int ws = p.waitStatus; // 如果节点被取消,或更改状态失败,则唤醒被阻塞的线程 if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread);
return true;
}
示例
public class Test { public synchronized static void main(String[] args) throws InterruptedException { Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { lock.lock(); System.out.println("线程1开始等待!"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1被唤醒继续执行结束!"); lock.unlock(); }, "1").start(); new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } lock.lock(); System.out.println("开始唤醒线程!"); condition.signal(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程2执行结束!"); lock.unlock(); }, "2").start(); } }