Condition is an alternative to WAIT and notify in JDK 1.5, so why not use wait and notify? Dude, I’m doing great with it. Don’t worry, buddy. Let me tell you more…

There are two reasons why Condition is recommended over Wait and notify in Object:

  1. usenotifyIn extreme circumstances, threads will be “suspended”.
  2. ConditionHigher performance.

Let’s use code and flow charts to illustrate both cases.

1. Notify thread “fake death”

The so-called thread “suspended animation” is referred to in usenotifyWaking up multiple waiting threads accidentally wakes up one thread that is not “ready”, causing the entire program to enter a blocked state and not be able to continue execution.

Using the producer and consumer model as an example of the classic case of multithreaded programming, let’s first demonstrate the problem of thread “suspended animation”.

1.1 Normal Version

Before demonstrating the thread “suspended animation” problem, let’s use Wait and notify to implement a simple producer and consumer model. To make the code more intuitive, I’ll write a super simple implementation version. Let’s create a factory class, which contains two methods, one is a circular production data (store) method, the other is a circular consumption data (retrieve) method, the implementation code is as follows.

/** * Factory class, consumers and producers use the factory class to produce/consume */
class Factory {
    private int[] items = new int[1]; // Data storage container (for demonstration purposes, set the capacity to store up to 1 element)
    private int size = 0;             // Actual storage size

    /** ** production method */
    public synchronized void put(a) throws InterruptedException {
        // Loop production data
        do {
            while (size == items.length) { // Note that it can't be if
                // The storage capacity is full, blocking waiting for consumers to wake up after consumption
                System.out.println(Thread.currentThread().getName() + "Entering the block");
                this.wait();
                System.out.println(Thread.currentThread().getName() + "Awakened");
            }
            System.out.println(Thread.currentThread().getName() + "Get to work");
            items[0] = 1; // Set a fixed value for demonstration purposes
            size++;
            System.out.println(Thread.currentThread().getName() + "Get the job done");
            // Wake up consumers when there is data in the production queue
            this.notify();

        } while (true);
    }

    /** * Consumption method */
    public synchronized void take(a) throws InterruptedException {
        // Recycle consumption data
        do {
            while (size == 0) {
                // The producer has no data and blocks to wait
                System.out.println(Thread.currentThread().getName() + "Entry blocking (consumer)");
                this.wait();
                System.out.println(Thread.currentThread().getName() + "Awakened (consumer)");
            }
            System.out.println("Consumer jobs ~");
            size--;
            // Wake up producers can now add production
            this.notify();
        } while (true); }}Copy the code

Let’s create two threads, one for the producer to call the PUT method and the other for the consumer to call the take method.

public class NotifyDemo {
    public static void main(String[] args) {
        // Create factory class
        Factory factory = new Factory();

        / / producer
        Thread producer = new Thread(() -> {
            try {
                factory.put();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Producer");
        producer.start();

        / / consumer
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Consumer"); consumer.start(); }}Copy the code

The result is as follows:As can be seen from the above results, producers and consumers are performing tasks in a cycle of harmony, which is the correct result we want.

1.2 Thread “suspended animation” version

The wait and notify methods do not cause any problems when there is only one producer and one consumer. However, when there are two producers, the thread is suspended.

public class NotifyDemo {
    public static void main(String[] args) {
		// Create a factory method.
        Factory factory = new Factory();

        / / producer
        Thread producer = new Thread(() -> {
            try {
                factory.put();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Producer");
        producer.start();

        // producer 2
        Thread producer2 = new Thread(() -> {
            try {
                factory.put();
            } catch(InterruptedException e) { e.printStackTrace(); }},Producer 2);
        producer2.start();
        
        / / consumer
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Consumer"); consumer.start(); }}Copy the code

The execution result of the program is as follows:From the above results, we can see that when we increase the number of producers to 2, we will cause the thread to “suspend execution”. When producer 2 is woken up and blocked again, the whole program cannot continue to execute.

Analysis of thread “suspended animation”

Let’s first mark the execution steps of the above program and get the following results:As can be seen from the above picture:When step 4 is performed, the producer is in working state, while producer 2 and consumer are in waiting state. At this point, the correct approach should be to wake up the consumer to consume, and then wake up the producer to continue working after the consumer finishes consuming. However, the producer woke up producer 2 by mistake, and producer 2 did not have the ability to continue execution because the queue was full, thus causing the whole program to block, the flow chart is as follows:

Correctly executing the process looks like this:

1.3 use Condition

To solve the problem of thread “suspension”, we can try to do this using Condition, which is a class in the JUC (java.util.concurrent) package and is created using a Lock Lock. Condition provides three important methods:

  • await: the correspondingwaitMethods;
  • signal: the correspondingnotifyMethods;
  • signalAll:notifyAllMethods.

Condition is used in a similar way to wait/notify. The Condition is used to acquire the lock and then wait and wake up the lock. The basic usage of Condition is as follows:

// Create Condition
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
/ / lock
lock.lock();
try {
    // Business method....
    
    // 1. Enter the waiting state
    condition.await();

    // 2. Wake up
    condition.signal();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}
Copy the code

Tip: Use Lock correctly

Remember that Lock’s lock. Lock () method cannot be placed inside a try block. If the Lock method is inside a try block, another method may throw an exception that causes it to unlock an unlocked object ina finally block. It calls the AQS tryRelease method (depending on the specific implementation class), throw IllegalMonitorStateException anomalies.

Return to theme

To return to the subject of this article, we useConditionTo implement thread communication can avoid the program “suspended animation” situation, becauseConditionCan create multiple waiting to be set to producers and consumers of the model as an example, we can use two waiting for set, used as a consumer waiting and wake up, another to awaken the producer, so as not to appear producers awakens the producers of the producer (producers can only arouse consumers, consumers can only awaken) so that the whole process will not be “suspended animation”, Its execution process is shown in the figure below:After understanding its basic flow, let’s look at the concrete implementation code.

The code to implement the factory based on Condition is as follows:

class FactoryByCondition {
    private int[] items = new int[1]; // Data storage container (for demonstration purposes, set the capacity to store up to 1 element)
    private int size = 0;             // Actual storage size
    // Create Condition
    private Lock lock = new ReentrantLock();
    // The producer's Condition object
    private Condition producerCondition = lock.newCondition();
    // The consumer's Condition object
    private Condition consumerCondition = lock.newCondition();

    /** ** production method */
    public void put(a) throws InterruptedException {
        // Loop production data
        do {
            lock.lock();
            while (size == items.length) { // Note that it can't be if
                // The producer enters the wait
                System.out.println(Thread.currentThread().getName() + "Entering the block");
                producerCondition.await();
                System.out.println(Thread.currentThread().getName() + "Awakened");
            }
            System.out.println(Thread.currentThread().getName() + "Get to work");
            items[0] = 1; // Set a fixed value for demonstration purposes
            size++;
            System.out.println(Thread.currentThread().getName() + "Get the job done");
            // Wake up the consumer
            consumerCondition.signal();
            try{}finally{ lock.unlock(); }}while (true);
    }

    /** * Consumption method */
    public void take(a) throws InterruptedException {
        // Recycle consumption data
        do {
            lock.lock();
            while (size == 0) {
                // The consumer blocks and waits
                consumerCondition.await();
            }
            System.out.println("Consumer jobs ~");
            size--;
            // Wake up the producer
            producerCondition.signal();
            try{}finally{ lock.unlock(); }}while (true); }}Copy the code

The implementation code for two producers and one consumer is as follows:

public class NotifyDemo {
    public static void main(String[] args) {
        FactoryByCondition factory = new FactoryByCondition();

        / / producer
        Thread producer = new Thread(() -> {
            try {
                factory.put();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Producer");
        producer.start();

        // producer 2
        Thread producer2 = new Thread(() -> {
            try {
                factory.put();
            } catch(InterruptedException e) { e.printStackTrace(); }},Producer 2);
        producer2.start();

        / / consumer
        Thread consumer = new Thread(() -> {
            try {
                factory.take();
            } catch(InterruptedException e) { e.printStackTrace(); }},"Consumer"); consumer.start(); }}Copy the code

The execution result of the program is as follows:It can be seen from the above results when usedCondition, producer, consumer and producer 2 will be executed alternately, and the execution results are in line with our expectations.

2. Performance problems

When we show that notify causes a thread to “suspend death”, we must have thought that if we replace notify with notifyAll, the thread will not “suspend death”.

This does solve the problem of thread “suspended animation”, but it also introduces a new performance problem, which is presented directly in the code.

Here is the improved code using WAIT and notifyAll:

/** * Factory class, consumers and producers call factory class to implement production/consumption function
class Factory {
    private int[] items = new int[1];   // Data storage container (for demonstration purposes, set the capacity to store up to 1 element)
    private int size = 0;               // Actual storage size

    /** * Production method *@throws InterruptedException
     */
    public synchronized void put(a) throws InterruptedException {
        // Loop production data
        do {
            while (size == items.length) { // Note that it can't be if
                // The storage capacity is full, blocking waiting for consumers to wake up after consumption
                System.out.println(Thread.currentThread().getName() + "Entering the block");
                this.wait();
                System.out.println(Thread.currentThread().getName() + "Awakened");
            }
            System.out.println(Thread.currentThread().getName() + "Get to work");
            items[0] = 1; // Set a fixed value for demonstration purposes
            size++;
            System.out.println(Thread.currentThread().getName() + "Get the job done");
            // Wake up all threads
            this.notifyAll();
        } while (true);
    }

    /** * Consumption method *@throws InterruptedException
     */
    public synchronized void take(a) throws InterruptedException {
        // Recycle consumption data
        do {
            while (size == 0) {
                // The producer has no data and blocks to wait
                System.out.println(Thread.currentThread().getName() + "Entry blocking (consumer)");
                this.wait();
                System.out.println(Thread.currentThread().getName() + "Awakened (consumer)");
            }
            System.out.println("Consumer jobs ~");
            size--;
            // Wake up all threads
            this.notifyAll();
        } while (true); }}Copy the code

It is still two producers plus one consumer, and the code is as follows:

public static void main(String[] args) {
    Factory factory = new Factory();
    / / producer
    Thread producer = new Thread(() -> {
        try {
            factory.put();
        } catch(InterruptedException e) { e.printStackTrace(); }},"Producer");
    producer.start();

    // producer 2
    Thread producer2 = new Thread(() -> {
        try {
            factory.put();
        } catch(InterruptedException e) { e.printStackTrace(); }},Producer 2);
    producer2.start();

    / / consumer
    Thread consumer = new Thread(() -> {
        try {
            factory.take();
        } catch(InterruptedException e) { e.printStackTrace(); }},"Consumer");
    consumer.start();
}
Copy the code

The result is as follows:It can be seen from the above results:When we callnotifyAllDoes not cause the thread “feign death”, but will cause all producers have been raised, but because there is only one to perform tasks, thus awakened all producers, only one will perform the right job, and the other is what also not stem, and then enter the waiting state, this behavior for the entire program, is redundant, It only increases the overhead of thread scheduling, which leads to the performance degradation of the whole program.

In contrast,Condition 的 await 和 signalMethod, even if there are multiple producers, the program will only wake up one valid producer to work, as shown in the following figure:Producer and producer 2 in turn are woken up to work alternately, so there is no extra overhead to do so compared tonotifyAllThe performance of the whole program will improve a lot.

conclusion

In this paper, we demonstrate the defects of wait method and notify/notifyAll method through codes and flow charts. There are two main defects of wait method and Notify /notifyAll method. One is that when notify is used in extreme environment, the program will be “suspended”. Another problem is the performance degradation caused by notifyAll, so the Condition class is strongly recommended for thread communication.

PS: One may ask why not use signalAll and notifyAll of Condition for performance comparison? And using signal and notifyAll? Suffice it to say, why use signalAll when you can do this with Signal? It’s like wearing a short sleeve in a heated room of 25 degrees. Why wear a padded jacket?

Follow the public account “Java Chinese community” to see more dry goods, check Github to find more exciting: github.com/vipstone/al…