Part of this article is excerpted from The Art of Concurrent Programming in Java
An overview of the
Any Java object has a monitor method, consisting of wait(), wait(long timeout), notify(), and notifyAll(), which work with the synchronized synchronization keyword. The wait-notification pattern can be implemented. The Condition interface also provides object-like monitor methods that, in conjunction with Lock, implement the wait-notification pattern
Object’s monitor method versus the Condition interface:
Compare the item | Object monitor method | Condition |
---|---|---|
precondition | Gets the monitor lock for the object | Call lock.lock () to get the Lock. Call lock.newcondition () to get the Condition object |
A method is called | Call directly such as object.wait() | Call directly such as condition.await() |
Number of waiting queues | a | multiple |
The current thread releases the lock and enters the wait queue | support | support |
The current thread releases the lock and enters the wait queue, and does not respond to interrupts in the wait state | Does not support | support |
The current thread releases the lock and enters the timeout wait state | support | support |
The current thread releases the lock and enters the wait state to some future time | Does not support | support |
Wakes up a thread in the wait queue | support | support |
Wake up all threads in the wait queue | support | support |
Interface sample
Condition defines two types of wait-notification methods that require the lock associated with the Condition object to be acquired before the current thread calls these methods. The Condition object is created by the Lock object (which calls the Lock object’s newCondition() method); in other words, the Condition depends on the Lock object
public class ConditionUserCase {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait(a) throws InterruptedException {
lock.lock();
try {
condition.await();
} finally{ lock.unlock(); }}public void conditionSignal(a) {
lock.lock();
try {
condition.signal();
} finally{ lock.unlock(); }}}Copy the code
After calling the await() method, the current thread releases the lock and waits, while another thread calls the signal() method of the Condition object, notifying the current thread, which returns from the await() method and has acquired the lock before returning
Condition partial method and description:
Method names | describe |
---|---|
void await() throws InterruptedException | The current thread enters the wait state until it is notified or interrupted. |
void awaitUninterruptibly() | The current thread enters the wait state until notified, and the method does not respond to interrupts. |
long awaitNanos(long nanosTimeout) throws InterruptedException | The current thread enters the wait state until notified, interrupted, or timed out. The return value indicates the remaining timeout time. |
boolean awaitUntil(Date deadline) throws InterruptedException | The current thread enters the wait state until notified, interrupted, or at a certain time. The method returns true if it is not notified by the specified time, false otherwise. |
void signal() | Wakes up a thread waiting on Condition that must acquire the lock associated with Condition before returning from the wait method. |
void signalAll() | Wake up all threads waiting on Condition. Threads that can return from the wait method must acquire the lock associated with Condition. |
Let’s take a closer look at how Condition is used with an example of a bounded queue
public class BoundedQueue<T> {
private Object[] items;
// Subscripts added, subscripts removed, and the current number of data
private int addIndex, removeIndex, count;
private Lock lock = new ReentrantLock();
private Condition notEmpty = lock.newCondition();
private Condition notFull = lock.newCondition();
public BoundedQueue(int size) {
items = new Object[size];
}
/** * add an element, if the array is full, add thread to wait until empty */
public void add(T t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[addIndex] = t;
if (++addIndex == items.length) {
addIndex = 0;
}
++count;
notEmpty.signal();
} finally{ lock.unlock(); }}/** * Removes an element from the header. If the array is empty, the deletion thread enters the wait state until a new element is added
@SuppressWarnings("unchecked")
public T remove(a) throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object x = items[removeIndex];
if (++removeIndex == items.length) {
removeIndex = 0;
}
--count;
notFull.signal();
return (T) x;
} finally{ lock.unlock(); }}}Copy the code
Implementation analysis
ConditionObject is synchronizer AbstractQueuedSynchronizer inner classes, each Condition object contains a queue (waiting queue), the queue is the key to the Condition object implementation wait – notifications
1. Wait in a queue
The wait queue is a FIFO queue. Each node in the queue contains a thread reference, which is the thread waiting on the Condition object. If a thread calls the condition.await () method, the thread releases the lock. Constructs the node, joins the wait queue, and enters the wait state
A Condition contains a wait queue. The Condition has references to the first and last nodes. The new node simply points nextWaiter, the original tail node, to it and updates the tail node. The process of a node referring to an update is not guaranteed with CAS because the thread calling the await() method must be the one that has acquired the lock, i.e. the process is thread-safe by the lock
In the monitor model of Object, an Object has a synchronization queue and a wait queue, while the Lock in the simultaneous sending of packets has a synchronization queue and multiple wait queues, and the corresponding relationship is shown in the figure:
2. Waiting for
Calling the await() method of Condition causes the current thread to enter the wait queue and release the lock while the thread state changes to wait. When returned from await(), the current thread must have acquired the lock associated with Condition
The await() method of Condition looks like this:
public final void await(a) throws InterruptedException {
// Check thread interrupt status
if (Thread.interrupted())
throw new InterruptedException();
// The current thread is wrapped as Node and queued
Node node = addConditionWaiter();
// Release the synchronization state, i.e. release the lock
int savedState = fullyRelease(node);
int interruptMode = 0;
// Check whether the node is in the synchronization queue. If not, wait
while(! isOnSyncQueue(node)) {// Suspend the thread
LockSupport.park(this);
if((interruptMode = checkInterruptWhileWaiting(node)) ! =0)
break;
}
// Contention synchronization state
if(acquireQueued(node, savedState) && interruptMode ! = THROW_IE) interruptMode = REINTERRUPT;// Clear the node in the condition queue that is not waiting for the condition
if(node.nextWaiter ! =null)
unlinkCancelledWaiters();
// If the waiting thread is interrupted, an exception is thrown
if(interruptMode ! =0)
reportInterruptAfterWait(interruptMode);
}
Copy the code
3. Inform
Calling the signal() method of Condition wakes up the node that has waited the longest in the wait queue (the first node) and moves the node to the synchronization queue before waking it up
Condition’s signal() method code looks like this:
public final void signal(a) {
// Check whether the current thread has acquired the lock
if(! isHeldExclusively())throw new IllegalMonitorStateException();
// Get the first node of the wait queue, move to the synchronization queue and wake up
Node first = firstWaiter;
if(first ! =null)
doSignal(first);
}
Copy the code
Condition’s signAll() method is equivalent to executing a signal() method for each node in the wait queue. The effect is to move all nodes in the wait queue to the synchronization queue and wake up the thread of each node