A second set of modifications is to use the status field kept in each node for purposes of controlling blocking, not spinning。 In the synchronizer framework, a queued thread can only return from an acquire operation if it passes the tryAcquire method defined in a concrete subclass; a single "released" bit does not suffice。 But control is still needed to ensure that an active thread is only allowed to invoke tryAcquire when it is at the head of the queue; in which case it may fail to acquire, and (re)block。 This does not require a per-node status flag because permission can be determined by checking that the current node's predecessor is the head。 And unlike the case of spinlocks, there is not enough memory contention reading head to warrant replication。 However, cancellation status must still be present in the status field。
The queue node status field is also used to avoid needless calls to park and unpark。 While these methods are relatively fast as blocking primitives go, they encounter avoidable overhead in the boundary crossing between Java and the JVM runtime and/or OS。 Before invoking park, a thread sets a "signal me" bit, and then rechecks synchronization and node status once more before invoking park。 A releasing thread clears status。 This saves threads from needlessly attempting to block often enough to be worthwhile, especially for lock classes in which lost time waiting for the next eligible thread to acquire a lock accentuates other contention effects。 This also avoids requiring a releasing thread to determine its successor unless the successor has set the signal bit, which in turn eliminates those cases where it must traverse
multiple nodes to cope with an apparently null next field unless signalling occurs in conjunction with cancellation。
Perhaps the main difference between the variant of CLH locks used in the synchronizer framework and those employed in other languages is that garbage collection is relied on for managing storage reclamation of nodes, which avoids complexity and overhead。 However, reliance on GC does still entail nulling of link fields when they are sure to never to be needed。 This can normally be done when dequeuing。 Otherwise, unused nodes would still be reachable, causing them to be uncollectable。
Some further minor tunings, including lazy initialization of the initial dummy node required by CLH queues upon first contention, are described in the source code documentation in the J2SE1。5 release。
Omitting such details, the general form of the resulting implementation of the basic acquire operation (exclusive, noninterruptible, untimed case only) is:
if (!tryAcquire(arg)) {
node = create and enqueue new node; pred = node's effective predecessor;
while (pred is not head node || !tryAcquire(arg)) { if (pred's signal bit is set)
park(); else
compareAndSet pred's signal bit to true; pred = node's effective predecessor;
}
head = node;
The ConditionObject class enables conditions to be efficiently integrated with other synchronization operations, again by fixing some design decisions。 This class supports only Java-style monitor access rules in which condition operations are legal only when the lock owning the condition is held by the current thread (See [4] for discussion of alternatives)。 Thus, a ConditionObject attached to a ReentrantLock acts in the same way as do built-in monitors (via Object。wait etc), differing only in method names, extra functionality, and the fact that users can declare multiple conditions per lock。
A ConditionObject uses the same internal queue nodes as synchronizers, but maintains them on a separate condition queue。 The signal operation is implemented as a queue transfer from the condition queue to the lock queue, without necessarily waking up the signalled thread before it has re-acquired its lock。