目录
三、Condition和ConditionObject结构梳理
根据上一篇理解了Doug Lea设计AQS的意图和思路,AQS作为抽象的模板方法在其中完成了大量的同步方法的封装,分为排他和共享两个模式。所以我们的理解思路就是先看看state值与虚拟双向链表【以及Condition链表的关系】,完了再梳理模板方法封装的同步方法。
一、双向链表的Node节点结构
/**
* Wait queue node class.
*
* <p>The wait queue is a variant of a "CLH" (Craig, Landin, and
* Hagersten) lock queue. CLH locks are normally used for
* spinlocks. We instead use them for blocking synchronizers, but
* use the same basic tactic of holding some of the control
* information about a thread in the predecessor of its node. A
* "status" field in each node keeps track of whether a thread
* should block. A node is signalled when its predecessor
* releases. Each node of the queue otherwise serves as a
* specific-notification-style monitor holding a single waiting
* thread. The status field does NOT control whether threads are
* granted locks etc though. A thread may try to acquire if it is
* first in the queue. But being first does not guarantee success;
* it only gives the right to contend. So the currently released
* contender thread may need to rewait.
*
* <p>To enqueue into a CLH lock, you atomically splice it in as new
* tail. To dequeue, you just set the head field.
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
*
* <p>Insertion into a CLH queue requires only a single atomic
* operation on "tail", so there is a simple atomic point of
* demarcation from unqueued to queued. Similarly, dequeuing
* involves only updating the "head". However, it takes a bit
* more work for nodes to determine who their successors are,
* in part to deal with possible cancellation due to timeouts
* and interrupts.
*
* <p>The "prev" links (not used in original CLH locks), are mainly
* needed to handle cancellation. If a node is cancelled, its
* successor is (normally) relinked to a non-cancelled
* predecessor. For explanation of similar mechanics in the case
* of spin locks, see the papers by Scott and Scherer at
* http://www.cs.rochester.edu/u/scott/synchronization/
*
* <p>We also use "next" links to implement blocking mechanics.
* The thread id for each node is kept in its own node, so a
* predecessor signals the next node to wake up by traversing
* next link to determine which thread it is. Determination of
* successor must avoid races with newly queued nodes to set
* the "next" fields of their predecessors. This is solved
* when necessary by checking backwards from the atomically
* updated "tail" when a node's successor appears to be null.
* (Or, said differently, the next-links are an optimization
* so that we don't usually need a backward scan.)
*
* <p>Cancellation introduces some conservatism to the basic
* algorithms. Since we must poll for cancellation of other
* nodes, we can miss noticing whether a cancelled node is
* ahead or behind us. This is dealt with by always unparking
* successors upon cancellation, allowing them to stabilize on
* a new predecessor, unless we can identify an uncancelled
* predecessor who will carry this responsibility.
*
* <p>CLH queues need a dummy header node to get started. But
* we don't create them on construction, because it would be wasted
* effort if there is never contention. Instead, the node
* is constructed and head and tail pointers are set upon first
* contention.
*
* <p>Threads waiting on Conditions use the same nodes, but
* use an additional link. Conditions only need to link nodes
* in simple (non-concurrent) linked queues because they are
* only accessed when exclusively held. Upon await, a node is
* inserted into a condition queue. Upon signal, the node is
* transferred to the main queue. A special value of status
* field is used to mark which queue a node is on.
*
* <p>Thanks go to Dave Dice, Mark Moir, Victor Luchangco, Bill
* Scherer and Michael Scott, along with members of JSR-166
* expert group, for helpful ideas, discussions, and critiques
* on the design of this class.
*/
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final AbstractQueuedSynchronizer.Node SHARED = new AbstractQueuedSynchronizer.Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final AbstractQueuedSynchronizer.Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL:
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile AbstractQueuedSynchronizer.Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile AbstractQueuedSynchronizer.Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
AbstractQueuedSynchronizer.Node nextWaiter;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final AbstractQueuedSynchronizer.Node predecessor() throws NullPointerException {
AbstractQueuedSynchronizer.Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, AbstractQueuedSynchronizer.Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
还是看看Daug Lea对Node的注释说明:
<p> 等待队列是“CLH”(Craig Landin Hagersten)变体是双向锁队列,通常用于自旋锁。使用双向队列来实现阻塞的同步所有的任务,但是使用基本的策略来控制当前节点的前置节点的线程信息,使用volatile state字段就控制了双向队列中的所有节点的同步阻塞,当前一个节点释放时,后一个节点会被标记为SIGNAL,队列中的每个节点作为一个【特殊通知类型】会持有管程中的等待线程,volatile state字段不控制线程授权的锁。一个线程可以调用acquire方法(排他或共享)去设置为队列头部,但是可能不成功,则需要重新等待。
<p> 想要进入CLH锁(调用enq方法),则默认会以一个新的节点加入队尾,要退出队列,只需将节点(当前Node)的Head字段值为空【链表找不到】。
<pre>
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
</pre>
<p> 想要插入队列则需要使用CAS原子操作插入双向队列的尾部,同样的如果想退出队列则需要将前置节点字段置位null。只是该操作需要耗时将后续的链组装成串,如果某些节点因为超时而取消、执行中断(interrupt)则也需要调整。
<p> 双向链表的前置链接在正常的CLH锁中不使用,主要是用于节点的取消操作,当节点取消后需要后继节点重新链接到一个未取消的节点。
<p> 一般情况next链实现阻塞机制,每个节点的线程id保存到自己的Node中(volatile Thread thread属性)。所以前一个节点执行完成后,会唤醒下一个节点也就确定了它是哪个线程。成功唤醒下一个节点后就需要避免与刚加入队列的节点的竞争(即新加入的节点一定在被成功唤醒的节点后面),所以必要时需要原子操作(CAS)更新到最后,防止某一个节点的next为空(则双向链表就断了)。(换句话说,下一个链接是一个优化这样我们就不需要逆向扫描了)
<p> 取消操作使用比较保守的算法(牺牲性能,一定保证双向链表完整)。
<p> CLH队列需要一个虚拟的节点头才能往下走,但是我们在构造器中并没有初始化Head节点(防止不使用的话浪费资源,懒加载或者叫懒初始化),所以头结点是在使用构造初始化第一个节点时设置。如下:
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, AbstractQueuedSynchronizer.Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
<p> 管程模型的条件队列在这里可以允许有多个,但是等待的线程使用节点与其他正常的节点一样,只是需要单独增加一个链表。而这些链表(多个ConditionObject对象,正常可以通过ReentrantLock的newCondition方法创建)只需要链接到一个普通的Node节点即可,因为当前的节点一定是独占模式(即Node的EXCLUSIVE属性不为空)。调用Condition的await时,节点插入添加队列中,调用signal时才真正地转到主队列(CLH)队列中,并且使用专门的字段nextWaiter来标记节点在哪个队列上。
waitStatus
每个节点都有自己的状态:(volatile int waitStatus)属性
/** 任务取消时的节点状态 */
static final int CANCELLED = 1;
/** 需要唤醒线程(当前节点的 volatile Thread thread属性)的状态 */
static final int SIGNAL = -1;
/** 需要等待条件队列满足的状态 */
static final int CONDITION = -2;
/** 下一个共享节点的应该无条件地传播 */
static final int PROPAGATE = -3;
二、AQS结构梳理
AQS唯一继承了AbstractOwnableSynchronizer父类,而其中只有一个属性用于存储排他模式的【当前】线程(并提供setter、getter方法),那么其他线程进入的时候只需要判断其线程是否为空 或者 当前线程是否与排他线程相同就可以排他了。
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements Serializable {
}
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
private static final long serialVersionUID = 3737899427754241961L;
protected AbstractOwnableSynchronizer() { }
/** The current owner of exclusive mode synchronization. */
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
设计了两个内部类Node(双向链表的节点)、ConditionObject(管程模型的条件队列、单向链表),两个内部类后面分析,其数据结构【属性】还是比较简单的,那么重要的就是内部类和操作方法(队列的同步操作、条件队列的插队)
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
implements Serializable {
/** volatle state【解决可见性和有序性】 + unsafe【CAS解决原子性】 */
private volatile int state;
private static final Unsafe unsafe = Unsafe.getUnsafe();
/** 虚拟双向链表的头和尾节点 */
private transient volatile Node head;
private transient volatile Node tail;
// 其他同步方法,以及虚拟双向量的操作方法,省略
}
三、Condition和ConditionObject结构梳理
/**
* {@code Condition} factors out the {@code Object} monitor
* methods ({@link Object#wait() wait}, {@link Object#notify notify}
* and {@link Object#notifyAll notifyAll}) into distinct objects to
* give the effect of having multiple wait-sets per object, by
* combining them with the use of arbitrary {@link Lock} implementations.
* Where a {@code Lock} replaces the use of {@code synchronized} methods
* and statements, a {@code Condition} replaces the use of the Object
* monitor methods.
*
* <p>Conditions (also known as <em>condition queues</em> or
* <em>condition variables</em>) provide a means for one thread to
* suspend execution (to "wait") until notified by another
* thread that some state condition may now be true. Because access
* to this shared state information occurs in different threads, it
* must be protected, so a lock of some form is associated with the
* condition. The key property that waiting for a condition provides
* is that it <em>atomically</em> releases the associated lock and
* suspends the current thread, just like {@code Object.wait}.
*
* <p>A {@code Condition} instance is intrinsically bound to a lock.
* To obtain a {@code Condition} instance for a particular {@link Lock}
* instance use its {@link Lock#newCondition newCondition()} method.
*
* <p>As an example, suppose we have a bounded buffer which supports
* {@code put} and {@code take} methods. If a
* {@code take} is attempted on an empty buffer, then the thread will block
* until an item becomes available; if a {@code put} is attempted on a
* full buffer, then the thread will block until a space becomes available.
* We would like to keep waiting {@code put} threads and {@code take}
* threads in separate wait-sets so that we can use the optimization of
* only notifying a single thread at a time when items or spaces become
* available in the buffer. This can be achieved using two
* {@link Condition} instances.
* <pre>
* class BoundedBuffer {
* <b>final Lock lock = new ReentrantLock();</b>
* final Condition notFull = <b>lock.newCondition(); </b>
* final Condition notEmpty = <b>lock.newCondition(); </b>
*
* final Object[] items = new Object[100];
* int putptr, takeptr, count;
*
* public void put(Object x) throws InterruptedException {
* <b>lock.lock();
* try {</b>
* while (count == items.length)
* <b>notFull.await();</b>
* items[putptr] = x;
* if (++putptr == items.length) putptr = 0;
* ++count;
* <b>notEmpty.signal();</b>
* <b>} finally {
* lock.unlock();
* }</b>
* }
*
* public Object take() throws InterruptedException {
* <b>lock.lock();
* try {</b>
* while (count == 0)
* <b>notEmpty.await();</b>
* Object x = items[takeptr];
* if (++takeptr == items.length) takeptr = 0;
* --count;
* <b>notFull.signal();</b>
* return x;
* <b>} finally {
* lock.unlock();
* }</b>
* }
* }
* </pre>
*
* (The {@link java.util.concurrent.ArrayBlockingQueue} class provides
* this functionality, so there is no reason to implement this
* sample usage class.)
*
* <p>A {@code Condition} implementation can provide behavior and semantics
* that is
* different from that of the {@code Object} monitor methods, such as
* guaranteed ordering for notifications, or not requiring a lock to be held
* when performing notifications.
* If an implementation provides such specialized semantics then the
* implementation must document those semantics.
*
* <p>Note that {@code Condition} instances are just normal objects and can
* themselves be used as the target in a {@code synchronized} statement,
* and can have their own monitor {@link Object#wait wait} and
* {@link Object#notify notification} methods invoked.
* Acquiring the monitor lock of a {@code Condition} instance, or using its
* monitor methods, has no specified relationship with acquiring the
* {@link Lock} associated with that {@code Condition} or the use of its
* {@linkplain #await waiting} and {@linkplain #signal signalling} methods.
* It is recommended that to avoid confusion you never use {@code Condition}
* instances in this way, except perhaps within their own implementation.
*
* <p>Except where noted, passing a {@code null} value for any parameter
* will result in a {@link NullPointerException} being thrown.
*
* <h3>Implementation Considerations</h3>
*
* <p>When waiting upon a {@code Condition}, a "<em>spurious
* wakeup</em>" is permitted to occur, in
* general, as a concession to the underlying platform semantics.
* This has little practical impact on most application programs as a
* {@code Condition} should always be waited upon in a loop, testing
* the state predicate that is being waited for. An implementation is
* free to remove the possibility of spurious wakeups but it is
* recommended that applications programmers always assume that they can
* occur and so always wait in a loop.
*
* <p>The three forms of condition waiting
* (interruptible, non-interruptible, and timed) may differ in their ease of
* implementation on some platforms and in their performance characteristics.
* In particular, it may be difficult to provide these features and maintain
* specific semantics such as ordering guarantees.
* Further, the ability to interrupt the actual suspension of the thread may
* not always be feasible to implement on all platforms.
*
* <p>Consequently, an implementation is not required to define exactly the
* same guarantees or semantics for all three forms of waiting, nor is it
* required to support interruption of the actual suspension of the thread.
*
* <p>An implementation is required to
* clearly document the semantics and guarantees provided by each of the
* waiting methods, and when an implementation does support interruption of
* thread suspension then it must obey the interruption semantics as defined
* in this interface.
*
* <p>As interruption generally implies cancellation, and checks for
* interruption are often infrequent, an implementation can favor responding
* to an interrupt over normal method return. This is true even if it can be
* shown that the interrupt occurred after another action that may have
* unblocked the thread. An implementation should document this behavior.
*
* @since 1.5
* @author Doug Lea
*/
public interface Condition {
void await() throws InterruptedException;
void awaitUninterruptibly();
/**
* 使用的正确姿势:
* <pre> {@code
* boolean aMethod(long timeout, TimeUnit unit) {
* long nanos = unit.toNanos(timeout);
* lock.lock();
* try {
* while (!conditionBeingWaitedFor()) {
* if (nanos <= 0L)
* return false;
* nanos = theCondition.awaitNanos(nanos);
* }
* // ...
* } finally {
* lock.unlock();
* }
* }}</pre>
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
/** 使用的正确姿势:
* <pre> {@code
* boolean aMethod(Date deadline) {
* boolean stillWaiting = true;
* lock.lock();
* try {
* while (!conditionBeingWaitedFor()) {
* if (!stillWaiting)
* return false;
* stillWaiting = theCondition.awaitUntil(deadline);
* }
* // ...
* } finally {
* lock.unlock();
* }
* }}</pre>
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
Condition相当于synchronized管程模型中,Object的wait、notify、notifyAll。预制对应的就是Condition定义的Condition的 await*、signal、signalAll方法。只是该管程模型中运行多个条件队列【synchronized(Object)只能放入一个对象,则只能执行一个条件队列】,而每一个条件队列(链表)与juc的Lock相对应。之前分析synchronized管程模型的正确使用姿势(并发编程基础 - synchronized使用场景和等待唤醒机制的正确姿势),即Object的等待唤醒方法如果不在锁中使用则会抛出异常,当然这里也规定了Condition管程模型的正确使用姿势(如上注释,并且也规定了awaitNanos、awaitUtil的正确使用姿势):
<pre>
class BoundedBuffer {
<b>final Lock lock = new ReentrantLock();</b>
final Condition notFull = <b>lock.newCondition(); </b>
final Condition notEmpty = <b>lock.newCondition(); </b>
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
<b>lock.lock();
try {</b>
while (count == items.length)
<b>notFull.await();</b>
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
<b>notEmpty.signal();</b>
<b>} finally {
lock.unlock();
}</b>
}
public Object take() throws InterruptedException {
<b>lock.lock();
try {</b>
while (count == 0)
<b>notEmpty.await();</b>
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
<b>notFull.signal();</b>
return x;
<b>} finally {
lock.unlock();
}</b>
}
}
</pre>
四、state与双向链表模型
梳理完上面的注释和AQS结构之后可以理解,在没有Condition对象时的模型,已经多个Condition队列时的模型对比: