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:
- use
notify
In extreme circumstances, threads will be “suspended”. Condition
Higher 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 usenotify
Waking 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 correspondingwait
Methods;signal
: the correspondingnotify
Methods;signalAll
:notifyAll
Methods.
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 useCondition
To implement thread communication can avoid the program “suspended animation” situation, becauseCondition
Can 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 callnotifyAll
Does 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
和 signal
Method, 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 tonotifyAll
The 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…