A list,

Before Lock, the synchronized keyword was the most commonly used method of synchronization. The wait/notification mode can be realized with the wait() and notify() methods of Object. The Condition interface also provides an Object-like monitor mode that works with Lock to implement the wait/notification mode, but the two differ in usage and functionality.

Some comparisons between the Object and Condition interfaces. From The Art of Concurrent Programming in Java




Condition interface introduction and examples

Condition depends on the lock object. This means that the Condition is created using the lock object (newCondition()) method). Using Condition is very simple. However, care needs to be taken to get the lock before calling the method.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionUseCase {

    public Lock lock = new ReentrantLock();
    public Condition condition = lock.newCondition();

    public static void main(String[] args)  {
        ConditionUseCase useCase = new ConditionUseCase();
        ExecutorService executorService = Executors.newFixedThreadPool (2);
        executorService.execute(new Runnable() {
            @Override
            public void run() { useCase.conditionWait(); }}); executorService.execute(newRunnable() {
            @Override
            public void run() { useCase.conditionSignal(); }}); } public voidconditionWait()  {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "Got the lock.");
            System.out.println(Thread.currentThread().getName() + "Waiting for signal");
            condition.await();
            System.out.println(Thread.currentThread().getName() + "Get the signal.");
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
    public void conditionSignal() {
        lock.lock();
        try {
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + "Got the lock.");
            condition.signal();
            System.out.println(Thread.currentThread().getName() + "Send out a signal"); }catch (Exception e){ }finally { lock.unlock(); }}}Copy the code

The running results are as follows:



As the example shows, the Condition object is typically used as a member variable. 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 interface common methods

Conditions can be popularly known as conditional queues. After calling the await() method, a thread is woken up until it knows that some condition is true. This approach provides a simpler wait/notification mode for threads. Condition must be used with locks because access to shared state variables occurs in a multithreaded environment. An instance of Condition must be bound to a Lock, so Condition is typically an internal implementation of Lock.

  • Await () : causes the current thread to wait until it receives a signal or is interrupted
  • Await (long time, TimeUnit unit) : cause the current thread to wait until it receives a signal, is interrupted, or reaches the specified wait time
  • AwaitNanos (Long nanosTimeout) : Causes the current thread to wait until it receives a signal, is interrupted, or reaches the specified wait time. The return value represents the remaining time, if nanosTimesout is awakened before nanosTimeout, then the return value = nanosTimeout – elapsed time, if the return value <= 0, it is considered timed out
  • AwaitUninterruptibly () : Causes the current thread to wait until it receives a signal. [Note: This method is not sensitive to interrupts]
  • AwaitUnit (Date Deadline) : Causes the current thread to wait until it receives a signal, is interrupted, or reaches a specified deadline. Returns true if not notified by the specified time, false otherwise.
  • Signal () : wakes up a waiting thread. The thread must acquire the lock associated with Condition before it returns from the wait method.
  • SignalAll () : Wakes up all waiting threads. Threads that can return from the waiting method must acquire locks associated with Condition.


4. Condition interface principle is briefly analyzed

Insert the wait thread

Condition is the inner class of AQS. Each Condition object contains a queue (wait 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 new node calls the condition.await () method, the thread will release the lock, construct the node to join the wait queue and enter the wait state. The basic structure of a wait queue is as follows:



Wait is divided into the first node and the last node. When a thread calls the condition.await () method, the node is constructed from the current node and queued from the tail. To add a node, point the tail node to the new node. Node reference updates are inherently done after the lock is acquired, so no CAS guarantee is required. It is also a thread-safe operation.


2, waiting for

After the thread calls the await() method, the thread is added to the wait queue as a node in the queue and ownership of the lock is released. When returned from the await() method, the lock associated with the condition must be obtained. When a node in the waiting queue is awakened, the thread that awakened the node attempts to obtain synchronization status. If the waiting thread is awakened by the employee conditional.signal () method instead of being dropped by another thread, InterruptedException is thrown.


3, notifications,

Calling the signal() method of Condition wakes up the node that has waited the longest in the wait queue (the first node in the conditional queue) and moves the node to the synchronous queue before waking it up.

The current thread joins the queue as shown in the figure below:




The lock must be determined before the signal() method can be called. The head node of the wait queue is then obtained, moved to the synchronization queue and woken up the thread in the node using LockSupport. The node is moved from the wait queue to the synchronization queue, as shown below:



The awakened thread exits from the while loop in the await() method. Then join the race for synchronized states. The thread that successfully obtains the contention returns to the state before the await() method.


Five, the summary

Call await() and add the current thread to the Condition queue. The current thread releases the lock, otherwise other threads will not be able to obtain the lock and deadlock will occur. The spin (while) suspends, constantly checking if the node is in the synchronization queue, if so, trying to acquire the lock, otherwise suspends. The current thread is awakened by the signal() method. The awakened thread exits from the while loop in the await() method and then calls the acquireQueue() method to contest the synchronization state.


Utilizing Condition to realize the producer-consumer model

import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class BoundedQueue { private LinkedList<Object> buffer; Private int maxSize; // What is the maximum value of the container? private Condition fullCondition; private Condition notFullCondition; BoundedQueue(int maxSize){ this.maxSize = maxSize; buffer = new LinkedList<Object>(); lock = new ReentrantLock(); fullCondition = lock.newCondition(); notFullCondition = lock.newCondition(); } /** * producer * @param obj * @throws InterruptedException */ public void put(Object obj) throws InterruptedException { lock.lock(); // Get lock try {while(maxSize == buffer.size()){ notFullCondition.await(); } buffer.add(obj); fullCondition.signal(); // notice} finally {lock.unlock(); }} /** * consumer * @return
     * @throws InterruptedException
     */
    public Object get() throws InterruptedException {
        Object obj;
        lock.lock();
        try {
            while(buffer.size() == 0){// There is no data in the queue and the thread enters the wait state fullcondition.await (); } obj = buffer.poll(); notFullCondition.signal(); // notice} finally {lock.unlock(); }returnobj; }}Copy the code


Condition of Java concurrency