Production and consumption model in multithreading

1, Production and consumption model

In real life, it is necessary to operate a shared resource (pool), but the operation methods of this shared resource are different (part is water injection [production] and part is water pumping [consumption]). This phenomenon can be called production and consumption model.

Production: it can be simulated with some threads. Multiple threads inject water into the pool at the same time.

Consumption: it can be simulated with some threads. Multiple threads pump water from the pool at the same time.

There are different operation modes for resources, each of which can be held by some threads. Multiple different threads operate differently on the same resources (supermarket, pool, etc.).

At this time, we cannot use a run method to encapsulate the task of the thread. Therefore, we need to define different thread task classes to describe the tasks of different threads.

Controlling the same resource through different thread operations belongs to the production and consumption model

II   Simple realization of production and consumption model

Create a public resource class
1. Create a public resource class

package com.wangxing.test1;
/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	//Save an array of shared resources [resource pool]
	private Object objs[]=new Object[1];
	//Methods of recording production and consumption
	private int num=1;
	
	//Method of production
	public void production(){
		objs[0]="water"+num;
		System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
		num++;
	}
	//Method of consumption
	public void delete(){
		System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
		objs[0]=null;
	}
}

2. Target class of production shared resources

package com.wangxing.test1;
/*
 * Target class of production shared resources
 */
public class product implements Runnable{
	//Define shared resource objects
	private Resoucre resoucre;
	//Pass in the shared resource object through the construction method
	public product(Resoucre resoucre){
		this.resoucre=resoucre;
	}
	//Override the run method of the Runnable interface (select this for sharing words)
	@Override
	public void run() {
		//Continuous water injection
		while(true){
			//Production method for accessing shared resources
			resoucre.production();
		}
	}
}

3. Target class for consumption of shared resources

package com.wangxing.test1;

public class XiaoFei implements Runnable{
	// Define shared resource objects
	private Resoucre resoucre;

	// Pass in the shared resource object through the construction method
	public XiaoFei(Resoucre resoucre){
			this.resoucre=resoucre;
		}

	// Override the run method of the Runnable interface (select this for sharing words)
	@Override
	public void run() {
		//continued
		while(true){
			// Production method for accessing shared resources
			resoucre.delete();
		}
	}
}

4. Test

package com.wangxing.test1;

import javax.annotation.Resource;

public class Main {

	public static void main(String[] args) {
		//Create shared resource class object
		Resoucre resoucre=new Resoucre();
		//Create a target class object for production shared resources
		product sc=new product(resoucre);
		//Create a target class object that consumes shared resources
		XiaoFei xFei=new XiaoFei(resoucre);
		//Create producer thread object	
		Thread scth=new Thread(sc);
		//Create consumer thread object
		Thread xfTh=new Thread(xFei);
		//Start production and consumption threads
		scth.start();
		xfTh.start();
	}
}

Sometimes it is null:

There are two threads: the producer is responsible for water injection and the consumer is responsible for water pumping.
Assuming that the CPU is on the consumer thread, when the consumer is about to print and the array space is null, the CPU switches to the producer before assigning null to the array space. After the producer injects water into the array space and has not printed, the CPU switches back to the consumer thread, and the consumer thread will immediately assign null to the array space. If the CPU switches back to the producer thread, the printed water injection is null.

Sometimes, the consumer is null:

There are two threads: the producer is responsible for water injection and the consumer is responsible for water pumping.
Assuming that the CPU is on the consumer thread, when the consumer finishes printing and pumps water as "water 1", before assigning null to the array space, the CPU switches to the producer. After the producer injects water into the array space, it prints out that the water to be injected is water 2. The CPU switches back to the consumer thread, and the consumer thread will immediately assign null to the array space. If the CPU switches back to the producer thread, it performs water injection times plus 1. If the CPU switches back to the consumer thread, the consumer thread will output null.

The above two problems are caused by the fact that when the current thread is accessing the shared resources, other threads can also access the shared resources. Therefore, thread synchronization is required when threads operate to share data.

Thread synchronization can ensure that water cannot be pumped during water injection, or water cannot be injected into the current space during water injection.
Modify the Resource to add synchronous code blocks for water injection and pumping methods to ensure that water cannot be pumped during water injection, or water cannot be injected into the current space during water injection.

package com.wangxing.test1;
/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	//Save an array of shared resources [resource pool]
	private Object objs[]=new Object[1];
	//Methods of recording production and consumption
	private int num=1;
	//Create synchronization object
	private static final Object loc=new Object();
	
	//Method of production
	public void production(){
		synchronized(loc){
			objs[0]="water"+num;
			System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
			num++;
		}
	}
	//Method of consumption
	public void delete(){
		synchronized(loc){
			System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
			objs[0]=null;
		}
	}
}

After the above implementation is completed, there will be the problem of multiple water injection without water pumping, or multiple water pumping without water injection?

To solve the above problem of multiple operations, we first need to judge whether the conditions of pumping or water injection are met.

When to pump water: pump water when the array space is not null.

When to inject water: the array space can only be injected when it is null.

If the water injection time is not satisfied, but the CPU is currently on the water injection thread, the water injection thread must wait until the water injection can be completed.

If the pumping time is not satisfied, but the CPU is currently on the pumping thread, the pumping thread must wait until the array has water to complete the pumping action.

You need to use the waiting and wake-up mechanism of threads in Java (communication between threads):

Wait: if it is judged that it is not satisfied, the thread will wait. Wait until the operation is satisfied before continuing.

After the water injection thread completes the water injection, the pumping thread should be told to pump water. Similarly, after the pumping thread has pumped water, it should tell the water injection thread that it can inject water.

Wake up: when one party's operation is completed, the waiting state of the thread waiting for the other party's operation needs to be restored to the operable state (the operation that one party notifies the other party is called thread wake-up).

In Java, two different methods are provided to represent wait and wake-up respectively:

The wait and wake-up methods are not defined in the Thread class, but in the Object class (because only the synchronized lock can make the Thread wait or wake up the waiting Thread, while the synchronized lock is any Object, and the wait and wake-up methods can only be defined in the Object class)

 void

wait () this object is called on another thread notify()   Method or notifyAll()   Method, causing the current thread to wait

 void

notify () wake up a single thread waiting on this object monitor.

 void

notifyAll () wake up all threads waiting on this object monitor.

Note: wait and wake (thread communication) must be in synchronization. Because waiting and wake-up must be completed using the current lock.
Modify the Resource to add thread waiting and wake-up operations for water injection and pumping methods

package com.wangxing.test1;
/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	//Save an array of shared resources [resource pool]
	private Object objs[]=new Object[1];
	//Methods of recording production and consumption
	private int num=1;
	//Create synchronization object
	private static final Object loc=new Object();
	
	//Method of production
	public void production() throws Exception{
		synchronized(loc){
			//Judge whether there is water in the array during production
			//If there is water, there is no need to inject water. If you just switch to the injection thread at this time,
			//Then the production thread should wait
			if (objs[0]!=null) {
				//Water injection thread waiting
				loc.wait();
			}
			objs[0]="water"+num;
			System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
			num++;
			//Wake up consumer thread running
			loc.notify();
		}
	}
	//Method of consumption
	public void delete() throws Exception{
		synchronized(loc){
			//Determine whether there is water in the array during consumption
			//If there is no water, there is no need to pump water. If you just switch to the pumping thread at this time,
			//Then the consuming thread should wait
			if(objs[0]==null){
				loc.wait();
			}
			System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
			objs[0]=null;
			//Wake up production thread
			loc.notify();
		}
	}
}

  The above program handles the single thread water injection and pumping action.

Next, we modify the program to the case of multiple water injection and pumping.
Modify the main class, create several more water injection and pumping Thread objects, and start running.

package com.wangxing.test1;

import javax.annotation.Resource;

public class Main {

	public static void main(String[] args) {
		//Create shared resource class object
		Resoucre resoucre=new Resoucre();
		//Create a target class object for production shared resources
		product sc=new product(resoucre);
		//Create a target class object that consumes shared resources
		XiaoFei xFei=new XiaoFei(resoucre);
		//Create producer thread object	
		Thread scth=new Thread(sc);
		Thread scth2=new Thread(sc);
		//Create consumer thread object
		Thread xfTh=new Thread(xFei);
		Thread xfTh2=new Thread(xFei);
		//Start production and consumption threads
		scth.start();
		scth2.start();
		xfTh.start();
		xfTh2.start();
	}
}

  The single water injection and single water pumping are modified into two water injection and two water pumping. As a result, the phenomenon of multiple water injection or multiple water pumping appears in the program.
The reason for this phenomenon is that when waking up, the pumping thread wakes up another pumping thread. Or the flooded thread wakes up another flooded thread. As long as its companion thread wakes itself up, the awakened thread can continue to operate. This leads to the phenomenon of multiple water injection or multiple water pumping.
Solve the above problem: change the if to judge whether there is water to while. After waking up, you can continue to judge.

package com.wangxing.test1;
/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	//Save an array of shared resources [resource pool]
	private Object objs[]=new Object[1];
	//Methods of recording production and consumption
	private int num=1;
	//Create synchronization object
	private static final Object loc=new Object();
	
	//Method of production
	public void production() throws Exception{
		synchronized(loc){
			//Judge whether there is water in the array during production
			//If there is water, there is no need to inject water. If you just switch to the injection thread at this time,
			//Then the production thread should wait
			while(objs[0]!=null) {
				//Water injection thread waiting
				loc.wait();
			}
			objs[0]="water"+num;
			System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
			num++;
			//Wake up consumer thread running
			loc.notify();
		}
	}
	//Method of consumption
	public void delete() throws Exception{
		synchronized(loc){
			//Determine whether there is water in the array during consumption
			//If there is no water, there is no need to pump water. If you just switch to the pumping thread at this time,
			//Then the consuming thread should wait
			while(objs[0]==null){
				loc.wait();
			}
			System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
			objs[0]=null;
			//Wake up production thread
			loc.notify();
		}
	}
}

  After changing to while, the program has a new problem: deadlock (all threads are waiting. There are no threads outside that can be executed).

Solution: you can only use notifyAll to wake up all threads. Every time you wake up, you wake up all threads. Even if you wake up your companions, it doesn't matter, because you have to continue to judge. In this way, you must wait, but there must be another thread in the wake-up, and they won't wait. They don't wait, they will operate, and when their operation is completed, they will wake up all.

package com.wangxing.test1;
/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	//Save an array of shared resources [resource pool]
	private Object objs[]=new Object[1];
	//Methods of recording production and consumption
	private int num=1;
	//Create synchronization object
	private static final Object loc=new Object();
	
	//Method of production
	public void production() throws Exception{
		synchronized(loc){
			//Judge whether there is water in the array during production
			//If there is water, there is no need to inject water. If you just switch to the injection thread at this time,
			//Then the production thread should wait
			while(objs[0]!=null) {
				//Water injection thread waiting
				loc.wait();
			}
			objs[0]="water"+num;
			System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
			num++;
			//Wake up consumer thread running
			loc.notifyAll();
		}
	}
	//Method of consumption
	public void delete() throws Exception{
		synchronized(loc){
			//Determine whether there is water in the array during consumption
			//If there is no water, there is no need to pump water. If you just switch to the pumping thread at this time,
			//Then the consuming thread should wait
			while(objs[0]==null){
				loc.wait();
			}
			System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
			objs[0]=null;
			//Wake up production thread
			loc.notifyAll();
		}
	}
}
package com.wangxing.test1;
/*
 * Target class of production shared resources
 */
public class product implements Runnable{
	//Define shared resource objects
	private Resoucre resoucre;
	//Pass in the shared resource object through the construction method
	public product(Resoucre resoucre){
		this.resoucre=resoucre;
	}
	//Override the run method of the Runnable interface (select this for sharing words)
	//Define shared production resources
	private int i=50;
	@Override
	public void run() {
		//Continuous water injection
		while(i>1){
			//Production method for accessing shared resources
			try {
				resoucre.production();
				i--;
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}	
	}
}

  In a multi production and multi consumption program, in order to ensure that all threads are not waiting, you can only use notifyAll to wake up all waiting threads when waking up. In this way, it can be guaranteed that there will be surviving threads every time. However, this wake-up efficiency is too low. It often happens that the producer wakes up his peer thread or the consumer wakes up his peer thread.

Provide the Condition interface in JDK5. It is used to replace the wait and wake-up mechanism.
java.util.concurrent.locks interface Condition
public interface Condition
Before JDK5, the wait and wake-up under a synchronous lock could not distinguish whether the current thread waiting or wake-up belonged to production or consumption. The Condition interface can create different waiting and waking objects, which can be used in different scenarios:
You can create a Condition object to be responsible for production.
You can create a Condition object that is specifically responsible for consumption.
The thread responsible for production can be monitored exclusively through the Condition object responsible for production. Monitor the thread of consumption through the Condition responsible for consumption. When waiting and waking up, you can use their own Condition objects.

 void

await () causes the current thread to be signaled or interrupt Has been waiting before.

 void

signal () wake up a waiting thread.

 void

signalAll () wake up all waiting threads.

Note: if you want to use the Condition interface, you must use the Lock interface for synchronization.
If the synchronization code block used in the program is synchronized, only the wait, notify and notifyAll methods in Object can be used for waiting and wake-up.
Only the Lock interface used for synchronization can use the Condition interface for waiting and waking up.
Others are the same as above. You only need to change the shared Resource class Resource class of shared data

package com.wangxing.test2;

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

/*
 * Shared resource class of shared data operated by multiple threads
 */
public class Resoucre {
	// Save an array of shared resources [resource pool]
	private Object objs[] = new Object[1];
	// Methods of recording production and consumption
	private int num = 1;
	// Create a Lock interface as a synchronized Lock
	private Lock lock = new ReentrantLock();
	// Create a thread to monitor production
	private Condition scCondition = lock.newCondition();
	// Create a thread to monitor consumption
	private Condition xfCondition = lock.newCondition();

	// Method of production
	public void production() {
		try {
			// Thread synchronization, acquire lock
			lock.lock();
			// Judge whether there is water in the array during production
			// If there is water, there is no need to inject water. If you just switch to the injection thread at this time,
			// Then the production thread should wait
			while (objs[0] != null) {
				// Production thread waiting
				scCondition.await();
			}
			objs[0] = "water" + num;
			System.out.println(Thread.currentThread().getName() + "In production:" + objs[0]);
			num++;
			// Wake up consumer thread running
			xfCondition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// close and lock
			lock.unlock();
		}
	}

	// Method of consumption
	public void delete(){
		try {
			//Thread synchronization, acquire lock
			lock.lock();
			// Determine whether there is water in the array during consumption
			// If there is no water, there is no need to pump water. If you just switch to the pumping thread at this time,
			// Then the consuming thread should wait
			while (objs[0] == null){
				//Consumer thread wait
				xfCondition.await();
			}
			System.out.println(Thread.currentThread().getName() + "In consumption:" + objs[0]);
			objs[0] = null;
			// Wake up production thread
			scCondition.signal();
		}catch (Exception e) {
				e.printStackTrace();
		}finally{
			//close and lock
			lock.unlock();
		}
	}
}

Why replace synchronous code blocks with Lock interfaces?

If we do not use the Lock interface to complete thread synchronization, we have to use the synchronization code [synchronized] to realize thread synchronization. If we use the synchronized code to synchronize threads, we can only use the wait, notify and notifyAll methods provided by the Object class. To achieve thread waiting and wake-up operations. The disadvantage is that the notifyAll method provided by the Object class will wake up all waiting threads. At this time, it may wake up its own peer thread. If it wakes up its own peer thread, the program will execute the judgment process of whether to inject water / pump water once more, so the execution efficiency of the program will be reduced. In order to improve the running efficiency of the program, we need to wake up only the water injection thread / water pumping thread when waking up the waiting thread, but not our companion thread. At this time, we need to use the waiting and wake-up methods provided by the Condition interface[ await(),signal(),signalAll ()], because it can wake up only the other thread, not the companion thread. When using the Condition interface, you need the newCondition method of the Lock interface object to create the Condition interface object. Therefore, we use the Lock interface object to realize thread synchronization instead of the synchronized code to realize thread synchronization.

Synchronization code [synchronized] - wait, notify and notifyAll methods provided by the Object class

The Lock interface object implements thread synchronization - provided by the Condition interface await(),signal(),signalAll () method

signalAll Under what circumstances does this wake up all threads use?

signalAll This method is provided by the Condition interface to wake up all waiting threads. It can be used in case of deadlock signalAll This method wakes up waiting threads of the same class.

There are two ways of waiting and wake-up mechanisms, which are different

The wait, notify, and notifyAll methods provided by the Object class

Provided by Condition interface await(),signal(),signalAll () method

Synchronization code [synchronized] realizes thread synchronization

Lock interface object to realize thread synchronization

Low efficiency

efficient

The difference between notify and notifyAll

notify

notifyAll

Wake up only one wait thread randomly

Wake up all wait threads

May cause deadlock

No deadlock

Wake up waiting threads are not separated from each other

signal And signalAll Differences between

signal

signalAll

Wake up only one wait thread randomly [same class]

Wake up all wait threads [same type]

The difference between sleep and wait

sleep

wait

Thread

Object

It depends on the system clock and CPU scheduling mechanism

The thread calls the notify() or notifyAll() methods

Do not release acquired lock resources

Release the acquired lock resource

Tags: Java Back-end

Posted on Tue, 30 Nov 2021 08:46:22 -0500 by poltort