Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

The principle of Java waiting and wake-up mechanism and the detailed explanation of producer-consumer model

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 "the principle of Java waiting and awakening mechanism and the detailed introduction of producer-consumer model". In daily operation, it is believed that many people have doubts about the principle of Java waiting and awakening mechanism and the detailed introduction of producer-consumer model. The editor consulted all kinds of materials and sorted out simple and useful operation methods. I hope it will be helpful for you to answer the doubts about "the principle of Java waiting and awakening mechanism and the detailed introduction of producer-consumer model". Next, please follow the editor to study!

The producer-consumer model is the most common pattern in multithreading: producer threads (one or more) generate bread and put it into a basket (collection or array), while consumer threads (one or more) take bread consumption from the basket (collection or array). Although they have different tasks, they deal with the same resources, which reflects a way of communication between threads.

This article will first explain the situation of single producer and single consumer, and then explain the situation of multi-producer and multi-consumer model. The two modes are also implemented using the wait () / nofity () / nofityAll () mechanism and the lock () / unlock () mechanism, respectively.

Before starting to introduce the pattern, explain the usage details of the wait (), notify (), and notifyAll () methods and the improved use of lock () / unlock (), await () / signal () / signalAll ().

1. The principle of waiting and awakening mechanism

Wait (), notify (), and notifyAll () refer to the thread that puts the thread to sleep, wakes up the sleep thread, and wakes up all sleep, respectively. But which thread is the object? In addition, all three methods described in the API document must be used under the premise of a valid monitor (which can be understood as holding a lock). What do these three methods have to do with locks?

Take the synchronization code block synchronized (obj) {} or synchronization functions as an example, wait (), notify (), and notifyAll () can be used in their code structure because they all hold locks.

For the following two synchronization code blocks, lock obj1 and lock obj2 are used respectively, in which thread 1 and thread 2 execute the synchronization code corresponding to obj1, and thread 3 and thread 4 execute the synchronization code corresponding to obj2.

Class MyLock implements Runnable {public int flag = 0; Object obj1 = new Object (); Object obj2 = new Object (); public void run () {while (true) {if (flag%2=0) {synchronized (obj1) {/ / Threads T1 and T2 perform this synchronization task / / try {obj1.wait () } catch (InterruptedException I) {} / / obj1.notify () / / obj1.notifyAll ()}} else {synchronized (obj2) {/ / Threads T3 and T4 perform this synchronization task / / try {obj2.wait ();} catch (InterruptedException I) {} / / obj2.notify () / / obj2.notifyAll ()}} class Demo {public static void main (String [] args) {MyLock ml = new MyLock (); Thread T1 = new Thread (ml) Thread T2 = new Thread (ml); Thread T3 = new Thread (ml); Thread T3 = new Thread (ml); t1.start (); t2.start (); try {Thread.sleep (1)} catch (InterruptedException I) {}; ml.flag++; t3.start (); t4.start ();}}

When T1 starts executing to wait (), it goes to sleep, but not in normal sleep, but in a thread pool identified by obj1 (actually the monitor corresponds to the thread pool, except that the monitor and lock are bound together). When T2 starts execution, it finds that the lock obj1 is held by another thread, and it will go to sleep, this time because of lock resource waiting rather than sleep entered by wait (). Because T2 has determined that it is applying for an obj1 lock, it will also sleep in the thread pool of obj1, rather than normal sleep. In the same way as T3 and T4, these two threads sleep in the obj2 thread pool.

When a thread executes to notify (), the notify () randomly wakes up any thread in the lock corresponding thread pool to which it belongs. For example, obj1.notify () will wake up any sleeping thread in the obj1 thread pool (of course, do nothing if there is no sleeping thread). By the same token, notifyAll () wakes up all sleeping threads in the thread pool corresponding to the lock to which it belongs.

What must be understood is the "corresponding lock", because the lock must be explicitly specified when calling wait (), notify (), and notifyAll (). For example, obj1.wait (). If the owning lock is omitted, the object this is represented, that is, the prefixes of these three methods can only be omitted in non-static synchronization functions.

In short, when synchronization is used, the lock is used, and the thread has ownership, and all its bases are determined by the lock to which it belongs. For example, when a thread is synchronized, it determines whether the lock is idle to determine whether to execute subsequent code, or whether to sleep in a specific thread pool, and when it wakes up, only the thread in the corresponding thread pool of the lock is awakened.

In the application of these methods, usually in a task, wait () and notify () / notifyAll () appear in pairs and are executed alternately. In other words, during this round of atomic synchronization, either wait () is executed to go to sleep, or notify () is executed to wake up sleeping threads in the thread pool. How to achieve alternative execution, you can consider the use of tags as a basis for judgment. Refer to the examples below.

2.Lock and Condition

The three methods in the wait () series are very limited because both sleep and awakening actions are completely coupled to the lock. For example, the thread associated with the lock obj1 can only wake up the thread in the obj1 thread pool, but not the thread associated with the lock obj2; for example, in the original synchronized synchronization, the lock is implicitly automatically acquired at the beginning of synchronization, and the lock is implicitly automatically released after performing an entire task, that is, the acquisition and release of the lock cannot be artificially controlled.

Starting with JDK 1.5, java provides the java.util.concurrent.locks package, which provides Lock, Condition, and ReadWriteLock interfaces, the first two of which decouple locks from monitor methods (sleep, wake-up operations). The Lock interface only provides locks, and one or more monitors associated with the lock can be generated by the lock method newConditon (). Each monitor has its own sleep and wake-up methods. In other words, Lock replaces the use of synchronized methods and synchronous code blocks, and Condition replaces the use of Object monitor methods.

As shown below:

When a thread executes condition1.await (), it sleeps in the thread pool corresponding to the condition1 monitor. When condition1.signal () is executed, any thread in the condition1 thread pool will be awakened randomly, and when condition1.signalAll () is executed, all threads in the condition1 thread pool will be awakened. The same is true for condition2 monitors.

Even if there are multiple monitors, you can operate on each other threads across monitors as long as they are associated with the same lock object. For example, a thread in condition1 can execute condition2.signal () to wake up a thread in the condition2 thread pool.

To use this lock and monitor association method, refer to the following steps:

Import java.util.concurrent.locks.*;Lock l = new ReentrantLock (); Condition con1 = l.newCondition (); condition con2 = l.newCondition (); l.lock (); try {/ / contains the code snippet of await (), signal () or signalAll ().} finally {l.unlock (); / / because the code snippet may be abnormal, but unlock () must be executed, you must use try and put unlock () into the finally segment}

For specific usage, see the sample code for Lock and condition later.

3. Single producer and single consumer model

A producer thread, a consumer thread, each time the producer produces a piece of bread and puts it on the plate, the consumer takes the bread from the plate and consumes it. Among them, producers judge whether to continue production based on the fact that there is no bread on the plate, while consumers judge whether to consume on the basis of bread on the plate. Since there is only one bread on the plate in this model, the plate can be omitted and producers and consumers can hand over the bread hand in hand.

First of all, you need to describe these three classes, one is the resources for multithreaded operation (in this case, bread), the second is the producer, and the third is the consumer. In the following example, I encapsulate the methods of producing and consuming bread into the producer and consumer classes, respectively, which is easier to understand if encapsulated in the bread class.

/ / description resource: the name and number of the bread. The number of the bread is determined by the number class Bread {public String name; public int count = 1; public boolean flag = false / / this tag provides judgment marks for wait () and notify (). / / producers and consumers deal with the same bread resources one after another, to ensure that bread can be designed according to singleton mode, or the same bread object can be passed to producers and consumers by construction, which is used here. / / describe the producer class Producer implements Runnable {private Bread b; / / the member of the producer: the resource Producer (Bread b) {this.b = b;} / / provide the method of producing bread public void produce (String name) {b.name = name + b.countcounter + } public void run () {while (true) {synchronized (Bread.class) {/ / uses Bread.class as the lock identifier, so that producers and consumers' synchronization code blocks can use the same lock if (b.flag) {/ / wait () must be inside the synchronization code block, not only because the lock must be held in order to sleep, but also there will be confusion in the judgment of the lock resource try {Bread.class.wait () } catch (InterruptedException I) {}} produce ("bread"); System.out.println (Thread.currentThread (). GetName () + "--producer -" + b.name); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = true; / / tag switching must also be kept synchronized Bread.class.notify () / / notify () must also be synchronized, otherwise the locks have been released and the wake-up action cannot be done / / ps: in a synchronization task, wait () and notify () should only be executed by one of them, otherwise the other thread will be confused} / / describe the consumer class Consumer implements Runnable {private Bread b; / / members of the consumer: the resource it processes Consumer (Bread b) {this.b = b } / / provide the method of consuming bread public String consume () {return b.name;} public void run () {while (true) {synchronized (Bread.class) {if (! b.flag) {try {Bread.class.wait ();} catch (InterruptedException I) {} System.out.println (Thread.currentThread (). GetName () + "--Consumer -" + consume ()); try {Thread.sleep (10) } catch (InterruptedException I) {} b.flag = false; Bread.class.notify ();}} public class ProduceConsume_1 {public static void main (String [] args) {/ / 1. Create the resource object Bread b = new Bread (); / / 2. Create a producer and consumer object and pass the same bread object to the producer and consumer Producer pro = new Producer (b); Consumer con = new Consumer (b); / / 3. Create thread object Thread pro_t = new Thread (pro); Thread con_t = new Thread (con); pro_t.start (); con_t.start ();}}

The final implementation result should be one produced and one consumed, so that there is a continuous cycle. As follows:

Thread-0---- producer-Bread 1ThreadMurray-Consumer-Bread 1ThreadMurray-Mermel-producer-Bread 2ThreadMurray 1Musco-Consumer-Bread 2ThreadMurray 0Meloft-producer-Bread 3ThreadMurray-Murray-Consumer- -Bread 3ThreadMurray-producer-Bread 4ThreadMurray-Mermel-Consumer-Bread 4ThreadMurray 0Murray-producer-Bread 5Threadmuri 1Musashi-Consumer-Bread 5ThreadMel 0Mube-producer-Bread 6ThreadMurray 1Mueller- Consumer-Bread 6

4. Using Lock and Condition to realize single production and single consumption pattern

The code is as follows:

Import java.util.concurrent.locks.*;class Bread {public String name; public int count = 1; public boolean flag = false; / / provides producers and consumers with the same lock object and the same Condition object public static Lock lock = new ReentrantLock (); public static Condition condition = lock.newCondition ();} class Producer implements Runnable {private Bread b; Producer (Bread b) {this.b = b;} public void produce (String name) {b.name = name + b.countcounter + } public void run () {while (true) {/ / use Bread.lock to lock resources Bread.lock.lock (); try {if (b.flag) {try {Bread.condition.await ();} catch (InterruptedException I) {}} produce ("bread"); System.out.println (Thread.currentThread (). GetName () + "--producer -" + b.name); try {Thread.sleep (10) } catch (InterruptedException I) {} b.flag = true; Bread.condition.signal ();} finally {Bread.lock.unlock ();}} class Consumer implements Runnable {private Bread b; Consumer (Bread b) {this.b = b;} public String consume () {return b.name;} public void run () {while (true) {/ / use Bread.lock to lock the resource Bread.lock.lock () Try {if (! b.flag) {try {Bread.condition.await ();} catch (InterruptedException I) {}} System.out.println (Thread.currentThread (). GetName () + "- Consumer -" + consume ()); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = false; Bread.condition.signal ();} finally {Bread.lock.unlock () }} public class ProduceConsume_1 {public static void main (String [] args) {/ / 1. Create the resource object Bread b = new Bread (); / / 2. Create a producer and consumer object and pass the same bread object to the producer and consumer Producer pro = new Producer (b); Consumer con = new Consumer (b); / / 3. Create thread object Thread pro_t = new Thread (pro); Thread con_t = new Thread (con); pro_t.start (); con_t.start ();}}

5. Multi-production and multi-consumption mode (single bread)

Here we first explain the model of multi-producers and multi-consumers, but there can be at most one bread at a time, which may not be ideal in practice, but in order to lead to the real multi-production and multi-consumption model, I think it is necessary to explain this pattern here and analyze how it evolved from the code of single production and single consumption.

As shown below:

From single production and single consumption to multi-production and multi-consumption, there are two issues that need to be considered because of multithread safety and deadlock issues:

For one party, how can multithreading achieve the same production or consumption capacity as a single thread? In other words, how to make multithreading look like a single thread. The biggest difference between multithreading and single threading lies in the safety of multithreading, so as long as the tasks executed by multithreading can be synchronized.

The first question considers the multi-threading problem of one party, and the second question considers how the two parties can cooperate harmoniously to complete the production and consumption. That is, how to ensure that one side of the producer and the consumer are active while the other is sleeping. You just need to wake up the other party when one side completes the synchronization task.

In fact, from single-threaded to multithreaded, there are two issues that need to be considered: asynchrony and deadlock. The main results are as follows: (1) when there is multithreading in both the producer and the consumer, the multithread of the producer can be regarded as a whole, and the multithread of the consumer can also be regarded as a whole, which solves the problem of thread safety. (2) then combine the production side and the consumer as a whole as multi-thread to solve the deadlock problem, and the way to solve the deadlock in java is to wake up each other or wake up all.

The question is how to ensure synchronization between multiple threads of one party? Take the code of single consumer executed by multi-thread as an example.

While (true) {synchronized (Bread.class) {if (! b.flag) {try {Bread.class.wait ();} catch (InterruptedException I) {}} System.out.println (Thread.currentThread (). GetName () + "- Consumer -" + consume ()); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = false; Bread.class.notify ();}}

Suppose consumer thread 1 wakes up consumer thread 2 after consuming a loaf of bread and continues to loop to determine that if (! flag) will wait, so the lock is released. Assuming that CPU happens to select consumer thread 2, consumer thread 2 will also enter wait. When the producer has produced a bread, suppose that consumer thread 1 is awakened, it will continue to consume the newly produced bread from the wait statement, and suppose it happens to awaken consumer thread 2 again. When consumer thread 2 is selected by CPU, consumer thread 2 will also consume down from the wait statement, consuming the bread just produced. The problem arises again, continuous awakening consumption thread 1 and 2 consume the same bread. In other words, the bread has been consumed repeatedly. This is again the problem of multi-thread asynchrony.

After talking for a long time, it is actually very simple to enlarge the line of sight and analyze it. As long as two or more threads of one side wait because of judging b.flag, then these two or more threads may be continuously awakened and continue to produce or consume. This causes the problem of multi-thread asynchrony.

The unsafe problem is that multiple threads on the same side continue to produce or consume down after a continuous awakening. This is caused by the if statement, and if you can let the thread of wait go back to determine whether the b.flag is true after waking up, you can let it decide whether to continue wait or down production or consumption.

You can replace the if statement with the while statement to meet the requirements. In this way, whether or not multiple threads on one side are continuously awakened, they will go back to determine the b.flag.

While (true) {synchronized (Bread.class) {while (! b.flag) {try {Bread.class.wait ();} catch (InterruptedException I) {}} System.out.println (Thread.currentThread (). GetName () + "- Consumer -" + consume ()); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = false; Bread.class.notify ();}}

The first multithread safety problem has been solved, but there will be a deadlock problem. This is easy to analyze, regarding the production side as a whole and the consumer side as a whole. When the production thread is wait (all the threads of the production side will wait when the thread of the production side is continuously awakened), and the consumer is wait, deadlock occurs. In fact, if you zoom in, you can see the production side and the consumer side as a thread, and these two threads form multithreaded. When one party cannot wake up the other party after wait, the other party will definitely wait, so it will be deadlocked.

The problem of deadlock between both parties can be solved as long as it can wake up each other, rather than continuously awakening the other party. You can either use notifyAll () or signalAll (), or you can wake up the other thread through signal (), as shown in the second paragraph of code below.

According to the above analysis, by improving the code of single production and single consumption mode, we can change it into a single bread mode of more production and more consumption.

/ / Code snippet 1class Bread {public String name; public int count = 1; public boolean flag = false;} / describe the producer class Producer implements Runnable {private Bread b; Producer (Bread b) {this.b = b;} public void produce (String name) {b.name = name + b.countdown; b.countkeeper;} public void run () {while (true) {synchronized (Bread.class) {while (b.flag) {try {Bread.class.wait () } catch (InterruptedException I) {}} produce ("bread"); System.out.println (Thread.currentThread (). GetName () + "--producer -" + b.name); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = true; Bread.class.notifyAll ();} / / describe consumer class Consumer implements Runnable {private Bread b Consumer (Bread b) {this.b = b;} public String consume () {return b.name;} public void run () {while (true) {synchronized (Bread.class) {while (! b.flag) {try {Bread.class.wait ();} catch (InterruptedException I) {}} System.out.println (Thread.currentThread (). GetName () + "- Consumer -" + consume ()) Try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = false; Bread.class.notifyAll ();} public class ProduceConsume_5 {public static void main (String [] args) {/ / 1. Create the resource object Bread b = new Bread (); / / 2. Create the producer and consumer object Producer pro = new Producer (b); Consumer con = new Consumer (b); / / 3. Create thread object Thread pro_t1 = new Thread (pro); / / production thread 1 Thread pro_t2 = new Thread (pro); / / production thread 2 Thread con_t1 = new Thread (con); / / consumption thread 1 Thread con_t2 = new Thread (con); / / consumption thread 2 pro_t1.start (); pro_t2.start (); con_t1.start (); con_t2.start ();}}

The following is the code refactored with Lock and Conditon, using signal () to wake up each other's threads.

/ / Code snippet 2import java.util.concurrent.locks.*;class Bread {public String name; public int count = 1; public boolean flag = false; public static Lock lock = new ReentrantLock (); public static Condition pro_con = lock.newCondition (); public static Condition con_con = lock.newCondition ();} / / describe the producer class Producer implements Runnable {private Bread b; Producer (Bread b) {this.b = b;} public void produce (String name) {b.name = name + b.countcounter + } public void run () {while (true) {Bread.lock.lock (); try {while (b.flag) {try {Bread.pro_con.await ();} catch (InterruptedException I) {}} produce ("bread"); System.out.println (Thread.currentThread (). GetName () + "--producer -" + b.name); try {Thread.sleep (10) } catch (InterruptedException I) {} b.flag = true; Bread.con_con.signal (); / / Wake up the consumer thread} finally {Bread.lock.unlock ();} / / describe the consumer class Consumer implements Runnable {private Bread b; Consumer (Bread b) {this.b = b;} public String consume () {return b.name;} public void run () {while (true) {Bread.lock.lock () Try {while (! b.flag) {try {Bread.con_con.await ();} catch (InterruptedException I) {}} System.out.println (Thread.currentThread (). GetName () + "- Consumer -" + consume ()); try {Thread.sleep (10);} catch (InterruptedException I) {} b.flag = false; Bread.pro_con.signal () / / Wake up the producer thread} finally {Bread.lock.unlock ();} public class ProduceConsume_6 {public static void main (String [] args) {/ / 1. Create the resource object Bread b = new Bread (); / / 2. Create the producer and consumer object Producer pro = new Producer (b); Consumer con = new Consumer (b); / / 3. Create thread objects Thread pro_t1 = new Thread (pro); Thread pro_t2 = new Thread (pro); Thread con_t1 = new Thread (con); Thread con_t2 = new Thread (con); pro_t1.start (); pro_t2.start (); con_t1.start (); con_t2.start ();}}

Make a summary on the problem of more production and more consumption:

(1)。 The solution to the multi-thread non-synchronization of one party is to use while (flag) to determine whether it is wait or not.

(2)。 The solution to the deadlock problem on both sides is to wake up each other, using notifyAll (), signalAll (), or the signal () method of the other's monitor.

6. Multi-production and multi-consumption mode

There are multiple producer threads and multiple consumer threads. The producer puts the bread into the basket (collection or array), and the consumer takes the bread out of the basket. Producers judge to continue production based on the basket is full, consumers judge to continue consumption on the basis of whether the basket is empty. In addition, when the consumer takes out the bread, the corresponding position is empty again, and the producer can go back to production from the starting position of the basket, which can be achieved by resetting the pointer of the basket.

In this model, in addition to describing producers, consumers, and bread, we also need to describe the basket as a container. Suppose an array is used as a container, the production pointer shifts backward for each producer, and the consumption pointer shifts backward for each consumer consumption.

The code is as follows: refer to the sample code given in the API-- > Condition class

Import java.util.concurrent.locks.*;class Basket {private Bread [] arr; / / the size of basket Basket (int size) {arr = new Bread [size];} / / the pointer of in and out private int in_ptr,out_ptr; / / how many breads left in basket private int left; private Lock lock = new ReentrantLock (); private Condition full = lock.newCondition (); private Condition empty = lock.newCondition (); / / bread into basket public void in () {lock.lock () Try {while (left = = arr.length) {try {full.await ();} catch (InterruptedException I) {i.printStackTrace ();}} arr [in _ ptr] = new Bread ("MianBao", Producer.num++); System.out.println ("Put the bread:" + arr [in _ ptr] .getName () + "- into basket [" + in_ptr+ "]"); left++; if (+ + in_ptr = arr.length) {in_ptr = 0 } empty.signal ();} finally {lock.unlock ();} / / bread out from basket public Bread out () {lock.lock (); try {while (left = = 0) {try {empty.await ();} catch (InterruptedException I) {i.printStackTrace ();}} Bread out_bread = arr [out _ ptr] System.out.println ("Get the bread:" + out_bread.getName () + "- from basket [" + out_ptr+ "]"); left--; if (+ + out_ptr = = arr.length) {out_ptr = 0;} full.signal (); return out_bread;} finally {lock.unlock ();}} class Bread {private String name; Bread (String name,int num) {this.name = name + num } public String getName () {return this.name;}} class Producer implements Runnable {private Basket basket; public static int num = 1; / the first number for Bread's name Producer (Basket b) {this.basket = b;} public void run () {while (true) {basket.in (); try {Thread.sleep (10);} catch (InterruptedException I) {}} class Consumer implements Runnable {private Basket basket; private Bread iGate; Consumer (Basket b) {this.basket = b } public void run () {while (true) {i_get = basket.out (); try {Thread.sleep (10);} catch (InterruptedException I) {} public class ProduceConsume_7 {public static void main (String [] args) {Basket b = new Basket (20); / / the basket size = 20 Producer pro = new Producer (b); Consumer con = new Consumer (b); Thread pro_t1 = new Thread (pro); Thread pro_t2 = new Thread (pro) Thread con_t1 = new Thread (con); Thread con_t2 = new Thread (con); Thread con_t3 = new Thread (con); pro_t1.start (); pro_t2.start (); con_t1.start (); con_t2.start (); con_t3.start ();}}

Here, consumers, producers, bread and baskets are involved, where bread and baskets are resources operated by multiple threads. The producer thread produces bread into the basket, and the consumer thread takes bread from the basket. The ideal code is to encapsulate both production and consumption tasks in the resource class, because bread is an element of the basket container, so it is not suitable to be encapsulated in the bread class, and encapsulated in the basket, which makes it easier to manipulate the container.

Note that all the code involved in resource manipulation must be placed inside the lock, otherwise there will be multi-thread non-synchronization problems. For example, the method produce () for producing bread is defined in the Producer class, and then used as a parameter to the method basket.in () that is put in the basket, that is, basket.in (producer ()), which is an error because produce () is passed to the in () method after the external execution of the lock.

At this point, the study on "the principle of Java waiting and awakening mechanism and the detailed introduction of producer-consumer model" 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.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report