In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-28 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/03 Report--
This article mainly introduces "Why not use Wait and Notify". In daily operation, I believe many people have doubts about why they do not use Wait and Notify. The editor consulted all kinds of materials and sorted out simple and easy-to-use methods of operation. I hope it will be helpful to answer the doubts of "Why not use Wait and Notify"! Next, please follow the editor to study!
1.notify thread "fake death"
The so-called thread "fake death" means that when using notify to wake up multiple waiting threads, it accidentally wakes up a thread that is not "ready", thus causing the whole program to enter a blocked state and can not continue to execute.
Taking the classic case producer and consumer model in multithreaded programming as an example, let's first demonstrate the problem of thread "fake death".
1.1 normal version
Before demonstrating the problem of thread "fake death", 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 here. Let's first create a factory class, which contains two methods, one is the (store) method of the circular production data, and the other is the (fetch) method of the circular consumption data. The implementation code is as follows.
/ * factory class, consumers and producers implement production / consumption by calling factory class * / class Factory {private int [] items = new int [1]; / / data storage container (set capacity up to 1 element for demonstration convenience) private int size = 0 / / actual storage size / * production method * / public synchronized void put () throws InterruptedException {/ / cycle production data do {while (size = = items.length) {/ / Note that the storage capacity cannot be determined by if / / is full System.out.println wakes up after waiting for consumers to consume (Thread.currentThread (). GetName () + "enter blocking") This.wait (); System.out.println (Thread.currentThread (). GetName () + "Wake up");} System.out.println (Thread.currentThread (). GetName () + "start working"); items [0] = 1; / / set a fixed value size++ for demonstration purposes System.out.println (Thread.currentThread (). GetName () + "work done"); / / notify consumers to wake up when there is data in the production queue this.notify ();} while (true) } / * consumption method * / public synchronized void take () throws InterruptedException {/ / cycle consumption data do {while (size = = 0) {/ / producer has no data, blocking waits for System.out.println (Thread.currentThread (). GetName () + "enter blocking (consumer)") This.wait (); System.out.println (Thread.currentThread (). GetName () + "awakened (consumer)");} System.out.println ("consumer work ~"); size--; / / awakening producer can add a production this.notify () } while (true);}}
Next, 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. The implementation code is as follows:
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 ();}}
The implementation results are as follows:
From the above results, it can be seen that producers and consumers are performing tasks alternately in a cycle, and the scene is very harmonious, which is the correct result we want.
1.2 Thread "fake death" version
When there is only one producer and one consumer, there is no problem with the wait and notify methods. However, the problem of thread "fake death" occurs when the producer is increased to two. The implementation code of the * * program is as follows:
Public class NotifyDemo {public static void main (String [] args) {/ / create a factory method (the code of the factory class remains the same and will not be repeated here) Factory factory = new Factory (); / / producer Thread producer = new Thread (()-> {try {factory ()) } 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 ();}}
The execution result of the program is as follows:
As can be seen from the above results, when we increase the number of producers to 2, it will cause the problem of thread "fake death" blocking execution. When producer 2 is awakened and blocked, the whole program can not continue to execute.
Analysis on the problem of thread "fake death"
Let's first mark the execution steps of the above program and get the following results:
As can be seen from the above figure, when the producer is working and the producer 2 and consumer are waiting, the correct thing to do at this time is to awaken the consumer to consume, and then wake up the producer to continue to work after the consumer has finished consuming. But at this time, the producer mistakenly awakens producer 2, and producer 2 does not have the ability to continue execution because the queue is full, thus blocking the whole program, as shown in the flow chart below:
The proper execution of the process should look like this:
1.3Use Condition
To solve the "fake death" problem of threads, we can try to implement it using Condition. Condition is a class under the JUC (java.util.concurrent) package and needs to be created using Lock locks. Condition provides three important methods:
Await: corresponding to the wait method
Signal: corresponding to the notify method
SignalAll: notifyAll method.
Condition is similar to wait/notify in that it first acquires the lock and then waits and wakes up in the lock. The basic usage of Condition is as follows:
/ / create Condition object Lock lock = new ReentrantLock (); Condition condition = lock.newCondition (); / / Lock lock.lock (); try {/ / Business method.... / / 1. Enter the waiting state condition.await (); / / 2. Wake up operation condition.signal ();} catch (InterruptedException e) {e.printStackTrace ();} finally {lock.unlock ();}
A little knowledge: the correct use of Lock posture
Keep in mind that the lock.lock () method of Lock cannot be put into the try code. If the lock method is within the try code block, it may be because other methods throw an exception, causing unlock to unlock the unlocked object in the finally code block. It will call the tryRelease method of AQS (depending on the specific implementation class) and throw an IllegalMonitorStateException exception.
Return to the theme
Going back to the topic of this article, if we use Condition to implement thread communication, we can avoid the "fake death" of the program, because Condition can create multiple wait sets. Taking the producer and consumer model of this article as an example, we can use two wait sets, one for consumers to wait and wake up, and the other to wake up producers. In this way, the producer will not wake up the producer (the producer can only awaken the consumer, the consumer can only awaken the producer), so that the whole process will not "fake death", and its execution process is shown in the following figure:
After understanding its basic process, let's look at the specific implementation code.
The Condition-based factory implementation code is as follows:
Class FactoryByCondition {private int [] items = new int [1]; / / data storage container (for demonstration convenience, set the capacity to store up to 1 element) private int size = 0; / / actual storage size / / create Condition object private Lock lock = new ReentrantLock (); / / producer Condition object private Condition producerCondition = lock.newCondition () / / Consumer Condition object private Condition consumerCondition = lock.newCondition (); / * * production method * / public void put () throws InterruptedException {/ / cycle production data do {lock.lock () While (size = = items.length) {/ / Note that the if cannot judge / / the producer enters and waits for System.out.println (Thread.currentThread (). GetName () + "entry blocking"); producerCondition.await (); System.out.println (Thread.currentThread (). GetName () + "awakened") } System.out.println (Thread.currentThread () .getName () + "start work"); items [0] = 1; / / to facilitate demonstration, set a fixed value size++; System.out.println (Thread.currentThread () .getName () + "get work done") / / Wake up consumers consumerCondition.signal (); try {} finally {lock.unlock ();}} while (true) } / * consumption method * / public void take () throws InterruptedException {/ / cycle consumption data do {lock.lock (); while (size = = 0) {/ / consumers block waiting for consumerCondition.await () } System.out.println ("Consumer work ~"); size--; / / Wake up producer producerCondition.signal (); try {} finally {lock.unlock ();}} while (true);}}
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 ();}}
The execution result of the program is shown in the following figure:
As can be seen from the above results, when using Condition, producers, consumers and producers will cycle around all the time, and the execution results are in line with our expectations.
two。 Performance problem
When we demonstrated above that notify will cause the problem of "fake death" of threads, some friends must have thought that if you replace notify with notifyAll threads, you will not be "fake death".
This approach can indeed solve the problem of thread "fake death", but at the same time there will be new performance problems, empty talk without evidence, directly on the code demonstration.
The following is the improved code using wait and notifyAll:
/ * Factory class, consumers and producers implement production / consumption functions by calling factory class. * / 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 () throws InterruptedException {/ / cycle production data do {while (size = = items.length) {/ / Note that the storage capacity cannot be determined by if / / is full System.out.println wakes up after waiting for consumers to consume (Thread.currentThread (). GetName () + "enter blocking") This.wait (); System.out.println (Thread.currentThread (). GetName () + "Wake up");} System.out.println (Thread.currentThread (). GetName () + "start working"); items [0] = 1; / / set a fixed value size++ for demonstration purposes System.out.println (Thread.currentThread (). GetName () + "get work done"); / / Wake up all threads this.notifyAll ();} while (true) } / * consumption method * @ throws InterruptedException * / public synchronized void take () throws InterruptedException {/ / Circular consumption data do {while (size = = 0) {/ / producer does not have data Blocking waiting for System.out.println (Thread.currentThread (). GetName () + "entry blocking (consumer)") This.wait (); System.out.println (Thread.currentThread (). GetName () + "Wake up (Consumer)");} System.out.println ("Consumer work ~"); size--; / / Wake up all threads this.notifyAll ();} while (true) }}
Still two producers plus one consumer, the implementation 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 ();}
The result of the execution is shown in the following figure:
From the above results, we can see that when we call notifyAll, it does not cause the thread to "fake death", but it will cause all the producers to be awakened, but because there is only one task to be executed, only one of the awakened producers will perform the correct work, while the other will do nothing and then enter the waiting state, which is undoubtedly unnecessary for the whole program. It will only increase the overhead of thread scheduling, resulting in a decline in the performance of the entire program.
In contrast to Condition's await and signal methods, even if there are multiple producers, the program will only awaken one valid producer to work, as shown in the following figure:
Producer and producer 2 will be awakened alternately to work, so there is no extra overhead when executing, so the performance of the whole program will be greatly improved compared to notifyAll.
At this point, the study on "Why not use Wait and Notify" is over. I hope to be able to solve your doubts. The collocation of theory and practice can better help you learn, go and try it! If you want to continue to learn more related knowledge, please continue to follow the website, the editor will continue to work hard to bring you more practical articles!
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.