java Multithread synchronization and inter thread communication

Posted by Stalingrad on Fri, 18 Feb 2022 13:24:40 +0100

1. Thread synchronization

What is thread synchronization? Let's first look at a piece of code of the ticket selling system, and then analyze this problem:

package com.test;
/**
 * @decrition Simulated ticket selling thread
 */
public class Ticket implements Runnable
{
	//Number of votes currently owned
	private  int num = 100;
	public void run()
	{
		while(true)
		{
			if(num>0)
			{
				try{
					Thread.sleep(10);
				}catch (InterruptedException e){}
					//Output ticket selling information
					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
			}
		}
	}
}

The above is the ticket selling thread class. Let's take a look at the execution class:

package com.test;
/**
 * @decrition Simulate the ticket selling system. In this case, only unilateral ticket selling is considered, and other situations are not considered for the time being
 */
public class TicketDemo {
	
	public static void main(String[] args) 
	{
		Ticket t = new Ticket();//Create a thread task object.
		
		//Create 4 threads to sell tickets at the same time
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		//Start thread
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

The results of running the program are as follows (only part of the data is intercepted):

It can be seen from the logic of the previous 4 threads running at the same time, which is the result of our selling tickets at the same time. Different threads operate on the same data, which easily leads to the problem of data disorder, that is, the threads are not synchronized. So how to solve this problem? Before giving the solution, let's analyze how this problem arises? We declare a thread class Ticket. In this class, we also declare a member variable num, that is, the number of votes. Then we continuously obtain the number of votes and output them through the run method. Finally, we create four threads in the external class TicketDemo to operate this data at the same time. After running, the thread synchronization problem we just mentioned occurs, From here, we can see that there are two conditions for thread synchronization (thread safety): 1 Data shared by multiple threads (Num), 2 There are multiple thread codes for operating shared data (4 threads); Now that the reason is known, how to solve it?
Solution: encapsulate the thread code of multiple operations sharing data. When a thread is executing these codes, other threads cannot participate in the operation. The current thread must execute all these codes before other threads can participate in the operation. OK, I know the idea. Let's use java code to solve this problem.

2. Two typical solutions to thread synchronization

There are two mechanisms in Java to prevent thread safety. The Java language provides a synchronized keyword to solve this problem. At the same time, in Java se5 0 introduces the related classes of Lock object. Next, we introduce these two methods respectively

2.1 solve thread safety problems by locking objects

Before giving the solution code, let's introduce a knowledge point: Lock, Lock object. In java, locks are used to control how multiple threads access shared resources. Generally speaking, a Lock can prevent multiple threads from accessing shared resources at the same time (but some locks can allow multiple threads to access shared resources concurrently, such as read-write locks, which will be analyzed later). Before the emergence of the Lock interface, java programs realized the Lock function by using the synchronized keyword (analyzed later), while java se5 After 0, the Lock interface is added to the concurrent package to realize the function of Lock. It provides the synchronization function similar to the synchronized keyword, but it needs to explicitly obtain and release the Lock when using. The disadvantage is that it lacks the convenience of implicitly obtaining and releasing the Lock like synchronized, but it has the operability of obtaining and releasing the Lock, Interruptible acquisition Lock, timeout acquisition Lock and other synchronization features that many synchronized keywords do not have. Next, let's introduce the main API s of the Lock interface to facilitate our study
Let's first introduce the API. Later, we will use some methods in combination with the implementation subclass ReentrantLock of the Lock interface.
ReentrantLock:
Reentry lock, as its name suggests, is a lock that supports re-entry. It means that the lock can support the repeated locking of resources by a thread, that is, when calling the lock() method, the thread that has obtained the lock can call the lock() method again to obtain the lock without being blocked. At the same time, it also supports the fairness and unfairness of obtaining the lock. The fairness here is that in absolute time, the request to obtain the lock first must be satisfied first, so the lock is a fair lock, on the contrary, it is unfair. So how to use it? See example code:
1. The code executed synchronously has similar functions to synchronized:

ReentrantLock lock = new ReentrantLock(); //The default parameter is false, which is unfair  
ReentrantLock lock = new ReentrantLock(true); //Fair lock  
  
lock.lock(); //If it is locked by other resources, it will wait for the lock to be released here to achieve the effect of suspension  
try {  
    //operation  
} finally {  
    lock.unlock();  //Release lock
}  

2. Prevent repeated code execution:

ReentrantLock lock = new ReentrantLock();  
if (lock.tryLock()) {  //If it has been lock ed, it will immediately return false and will not wait, so as to achieve the effect of ignoring the operation   
    try {  
        //operation  
    } finally {  
        lock.unlock();  
   }  
}  

3. Code trying to wait for execution:

ReentrantLock lock = new ReentrantLock(true); //Fair lock  
try {  
    if (lock.tryLock(5, TimeUnit.SECONDS)) {      
        //If it has been locked, try to wait for 5s to see if the lock can be obtained. If the lock cannot be obtained after 5s, return false to continue  
       try {  
            //operation  
        } finally {  
            lock.unlock();  
        }  
    }  
} catch (InterruptedException e) {  
    e.printStackTrace(); //When the current thread is interrupted, an InterruptedException will be thrown                   
}  

There is something to pay special attention to here. It is very important to put the unlocking operation in the finally code block. If the code in the critical area throws an exception, the lock must be released. Otherwise, other threads will block forever. OK, let's briefly introduce ReentrantLock here. Next, we use ReentrantLock to solve the thread synchronization (Security) problem of the previous ticket selling thread. The code is as follows:

package com.test;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * @decrition Simulated ticket selling thread
 */
public class Ticket implements Runnable {
    //Create lock object
    private Lock ticketLock = new ReentrantLock();
    //Number of votes currently owned
    private int num = 100;
 
    public void run() {
        while (true) {
            try {
                ticketLock.lock();//Lock acquisition
                if (num > 0) {
                    Thread.sleep(10);//Output ticket selling information system out. println(Thread.currentThread(). getName()+".....sale...."+ num--); }
                } else {
                    break;
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();//Interrupt in case of exception
            } finally {
                ticketLock.unlock();//Release lock
            }
        }
    }
}

There is no need to change the TicketDemo class. The running results are normal (too many are not posted). The thread safety problem is solved.

2.2 solve thread safety problems through synchronized keyword

The language level synchronization primitive synchronized is built in Java, which can greatly simplify the use of multi-threaded synchronization in Java. From Java SE1 Starting from 0, every object in Java has an internal lock. If a method is declared with the synchronized keyword, the object will protect the whole method, that is, the thread calling the method must obtain the internal object lock.

public synchronized void method{
  //method body
}

Equivalent to

private Lock ticketLock = new ReentrantLock();
public void method{
	ticketLock.lock();
	try{
	//.......
	}finally{
	 	ticketLock.unlock();
	}
}

As you can see here, writing code using the synchronized keyword is much simpler. Of course, to understand this code, we must know that each object has an internal lock and that the lock has an internal condition. The lock manages the threads that try to enter the synchronized method, and the condition manages the threads that call wait (wait()/notifyAll/notify()). At the same time, we must understand that once a thread obtains the internal lock through the synchronized method, all the synchronized methods or code blocks of this class cannot be accessed by other threads until the current thread releases the internal lock. The synchronization method mentioned above just now. Synchronized also has an implementation method of synchronizing code blocks:

Object obj = new Object();
synchronized(obj){
//Code that needs to be synchronized
}

Where obj is an object lock and can be any object. Then we will solve the thread synchronization problem of the ticketing system through one of the methods:

class Ticket implements Runnable
{
	private  int num = 100;
	Object obj = new Object();
	public void run()
	{
		while(true)
		{
			synchronized(obj)
			{
				if(num>0)
				{
					try{
					Thread.sleep(10);
					}catch (InterruptedException e){
					}
					
					System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
				}
			}
		}
	}
}

Well, the synchronization code block is solved, and the operation results are normal. The synchronization problem is solved. Of course, code synchronization also needs to sacrifice efficiency:
Benefits of synchronization: it solves the safety problem of threads.
Disadvantages of synchronization: it relatively reduces the efficiency, because all threads outside the synchronization will judge the synchronization lock.
Premise of synchronization: there must be multiple threads in synchronization and use the same lock.

3. Communication mechanism between threads

Threads start running and have their own stack space, but if each running thread only runs in isolation, it has no value or little value. If multiple threads can cooperate with each other to complete the work, it will bring great value, that is, the communication between threads. In java, the communication between multithreads is realized by waiting / notification mechanism.

3.1 synchronized keyword waiting / notification mechanism

It means that one thread A calls the wait() method of object o to enter the waiting state, while another thread B calls the notify() or notifyAll() method of object O. thread A returns from the wait() method of object o after receiving the notification, and then performs subsequent operations. The above two threads interact through object o, and the relationship between wait() and notify()/notifyAll() on the object is like A switch signal, which is used to complete the interaction between the waiting party and the notifying party.
The main function methods used in the waiting / notification mechanism are notify()/notifyAll(),wait()/wait(long),wait(long,int). These methods have been described in the previous article and will not be repeated here. Of course, this is a function or code block modified by the synchronized keyword, because the premise of using notify() / notifyall() / wait() / wait (long), wait (long, int) methods is to lock the calling object, that is, it can only be used in synchronization functions or synchronization code blocks.

3.2 waiting / notification mechanism of condition object

The so-called condition object is to cooperate with the Lock object we analyzed earlier to realize the waiting / notification mechanism through the condition object of the Lock object. So how are conditional objects created?

//Create condition object
Condition conditionObj=ticketLock.newCondition();

So we created a condition object. Note that the object returned here is the condition object related to the ticketLock. The following is the API of the condition object:

Process analysis of the above method: one thread A calls the await () method of the condition object to enter the waiting state, while another thread B calls the signal () or signalall () method of the condition object. After receiving the notification, thread A returns from the await () method of the condition object, and then performs subsequent operations. The above two threads complete the interaction through the condition object, and the relationship between await() and signal() / signalAll() on the object is just like the switch signal, which is used to complete the interaction between the waiting party and the notifying party. Of course, such operations must be based on the object lock. Only when the current thread obtains the lock can it call the await () method of the conditional object. After calling, the current thread will release the lock.
It should be noted that in the above two waiting / notification mechanisms, whether the signal() / signalAll() method or the notify()/notifyAll() method is called, a waiting thread will not be activated immediately. They only release the blocking state of waiting threads, so that these threads can access objects by competing for CPU execution rights after the current thread unlocks or exits the synchronization method. Now, after analyzing the concept of thread communication mechanism, let's implement the waiting / notification mechanism through the producer consumer mode.

4. Producer consumer model

4.1 single producer and single consumer model

As the name suggests, it is a thread consumption and a thread production. Let's first look at the producer consumer model under the waiting / notification mechanism: let's assume that we are selling Beijing roast duck shops. Now we have only one production line and only one consumption line, that is, we can only sell after the production thread is finished, and then notify the consumer thread. If the consumer thread has no roast duck, we must notify the production thread to produce, At this point, the consumption thread enters the waiting state. In such a scenario, we should not only ensure the thread safety of shared data (number of roast ducks), but also ensure that the number of roast ducks must have roast ducks before consumption. Let's implement it through java code:

Beijing roast duck production resource KaoYaResource:

package com.test;
/**
 * @decrition Roast duck resources
 */
public class KaoYaResource {
	
	private String name;
	private int count = 1;//Initial number of roast ducks
	private boolean flag = false;//Determine whether there is a flag that requires a thread to wait
	
	/**
	 * Roast duck production
	 */
	public synchronized void product(String name){
		if(flag){
			//At this time, there is roast duck, waiting
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace()
;
			}
		}
		this.name=name+count;//Set the name of the roast duck
		count++;
		System.out.println(Thread.currentThread().getName()+"...producer..."+this.name);
		flag=true;//Change sign after roast duck
		notifyAll();//Notify the consuming thread that it is ready to consume
	}
	
	/**
	 * Consumption roast duck
	 */
	public synchronized void consume(){
		if(!flag){//If there is no roast duck, wait
			try{this.wait();}catch(InterruptedException e){}
		}
		System.out.println(Thread.currentThread().getName()+"...consumer........"+this.name);//Consumption of roast duck 1
		flag = false;
		notifyAll();//Inform the producer to produce roast duck
	}
}

In this class, we have two synchronized synchronization methods, one for producing roast duck and the other for consuming roast duck. The reason why synchronization is needed is that we operate the shared data count. At the same time, in order to ensure that we can consume roast duck after producing roast duck, that is, we can consume roast duck after producing a roast duck, we use the wait / notification mechanism, wait() and notify(). When the production site is run for the first time, the production method is called. At this time, there is a roast duck, that is, flag=false. There is no need to wait. Therefore, we set the name of the consumable roast duck, change flag=true, and notify the consumer thread that the roast duck can be consumed. Even if the production thread grabs the execution right again, because flag=true, the production thread will enter the waiting blocking state, After the consumption thread is awakened, it enters the consumption method. After the consumption is completed, it changes the flag flag=false to inform the production thread that it can produce roast duck... This cycle.

Production and consumption execution class Single_Producer_Consumer.java:

package com.zejian.test;
/**
 * @decrition Single producer single consumer model
 */
public class Single_Producer_Consumer {
	
	public static void main(String[] args) 
	{
		KaoYaResource r = new KaoYaResource();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		//Producer thread
		Thread t0 = new Thread(pro);
		//Consumer thread
		Thread t2 = new Thread(con);
		//Start thread
		t0.start();
		t2.start();
	}
}
/**
 * @decrition Producer thread
 */
class Producer implements Runnable
{
	private KaoYaResource r;
	Producer(KaoYaResource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.product("Beijing Roast Duck");
		}
	}
}
/**
 * @decrition Consumer thread
 */
class Consumer implements Runnable
{
	private KaoYaResource r;
	Consumer(KaoYaResource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.consume();
		}
	}
}

In this class, we create two threads, one is the consumer thread and the other is the producer thread. We open these two threads respectively for continuous production and consumption. The running results are as follows:

Obviously, the situation is to produce a roast duck and then consume a roast duck. The operation is completely normal. Well, this is the single producer single consumer model. The above is implemented by using the synchronized keyword. Next, we use the object lock:

KaoYaResourceByLock.java

package com.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @decrition The waiting / notification mechanism is implemented by object locking
 */
public class KaoyaResourceByLock {
	
	private String name;
	private int count = 1;//Initial number of roast ducks
	private boolean flag = false;//Determine whether there is a flag that requires a thread to wait
	//Create a lock object
	private Lock resourceLock=new ReentrantLock();
	//Create condition object
	private Condition condition= resourceLock.newCondition();
	/**
	 * Roast duck production
	 */
	public  void product(String name){
		resourceLock.lock();//Get lock first
		try{
			if(flag){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name=name+count;//Set the name of the roast duck
			count++;
			System.out.println(Thread.currentThread().getName()+"...producer..."+this.name);
			flag=true;//Change sign after roast duck
			condition.signalAll();//Notify the consuming thread that it is ready to consume
		}finally{
			resourceLock.unlock();
		}	
	}
	
	/**
	 * Consumption roast duck
	 */
	public  void consume(){
		resourceLock.lock();
		try{
		if(!flag){//If there is no roast duck, wait
			try{condition.await();}catch(InterruptedException e){}
		}
		System.out.println(Thread.currentThread().getName()+"...consumer........"+this.name);//Consumption of roast duck 1
		flag = false;
		condition.signalAll();//Inform the producer to produce roast duck
		}finally{
			resourceLock.unlock();
		}
	}
}

The code changes little. We implement it through object lock. First, we need to create an object lock. The reentrant lock ReestrantLock class we use here, and then manually set lock() and unlock() to obtain and release the lock. In order to implement the waiting / notification mechanism, we must also create a Condition object Condition through the lock object, and then implement the waiting and notification operations through the await() and signalAll() methods of the lock object. Single_ Producer_ Consumer. Just replace the resource class with java code, and the running results will not be posted. If you are interested, you can operate by yourself.

4.2 multi producer and multi consumer model

After analyzing the single producer and single consumer model, let's talk about the multi producer and multi consumer model, that is, multiple production threads cooperate with multiple consumption threads. If that's the case with Single_Producer_Consumer.java class is changed into a new class, most of the codes remain unchanged, only two threads are added to run, and one t1 production shared resource class KaoYaResource is not changed. The code is as follows:

package com.zejian.test;
/**
 * @decrition Multi producer and multi consumer model
 */
public class Mutil_Producer_Consumer {
	
	public static void main(String[] args) 
	{
		KaoYaResource r = new KaoYaResource();
		Mutil_Producer pro = new Mutil_Producer(r);
		Mutil_Consumer con = new Mutil_Consumer(r);
		//Producer thread
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		//Consumer thread
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		//Start thread
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
/**
 * @decrition Producer thread
 */
class Mutil_Producer implements Runnable
{
	private KaoYaResource r;
	Mutil_Producer(KaoYaResource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.product("Beijing Roast Duck");
		}
	}
}
/**
 * @decrition Consumer thread
 */
class Mutil_Consumer implements Runnable
{
	private KaoYaResource r;
	Mutil_Consumer(KaoYaResource r)
	{
		this.r = r;
	}
	public void run()
	{
		while(true)
		{
			r.consume();
		}
	}
}

There are two more threads. Let's run the code and see the results as follows:


No, we only produced one roast duck. How come it was consumed three times? Some roast ducks were produced but not consumed? Is there no thread synchronization for shared data sources? Let's take a look at the previous kaoyaresource java

package com.test;
/**
 * @decrition Roast duck resources
 */
public class KaoYaResource {
	
	private String name;
	private int count = 1;//Initial number of roast ducks
	private boolean flag = false;//Determine whether there is a flag that requires a thread to wait
	
	/**
	 * Roast duck production
	 */
	public synchronized void product(String name){
		if(flag){
			//At this time, there is roast duck, waiting
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name=name+count;//Set the name of the roast duck
		count++;
		System.out.println(Thread.currentThread().getName()+"...producer..."+this.name);
		flag=true;//Change sign after roast duck
		notifyAll();//Notify the consuming thread that it is ready to consume
	}
	
	/**
	 * Consumption roast duck
	 */
	public synchronized void consume(){
		if(!flag){//If there is no roast duck, wait
			try{this.wait();}catch(InterruptedException e){}
		}
		System.out.println(Thread.currentThread().getName()+"...consumer........"+this.name);//Consumption of roast duck 1
		flag = false;
		notifyAll();//Inform the producer to produce roast duck
	}
}

The methods for obtaining the shared data count are synchronized with the synchronized keyword! How can there be data confusion?
Analysis: indeed, we have adopted synchronization measures for shared data and also applied the waiting / notification mechanism, but such measures can only be correctly applied in the case of single producer and single consumer. However, from the operation results, our previous single producer and single consumer security measures are not suitable for the case of multiple producers and multiple consumers. So what's the problem? We can clearly tell you that it must be in the resource sharing class. Let's analyze how the problem appears and how to solve it? Directly above

The resolved resource code is as follows. Only if is changed to while:

package com.test;
/**
 * @decrition Roast duck resources
 */
public class KaoYaResource {
	
	private String name;
	private int count = 1;//Initial number of roast ducks
	private boolean flag = false;//Determine whether there is a flag that requires a thread to wait
	/**
	 * Roast duck production
	 */
	public synchronized void product(String name){
		while(flag){
			//At this time, there is roast duck, waiting
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name=name+count;//Set the name of the roast duck
		count++;
		System.out.println(Thread.currentThread().getName()+"...producer..."+this.name);
		flag=true;//Change sign after roast duck
		notifyAll();//Notify the consuming thread that it is ready to consume
	}
	
	/**
	 * Consumption roast duck
	 */
	public synchronized void consume(){
		while(!flag){//If there is no roast duck, wait
			try{this.wait();}catch(InterruptedException e){}
		}
		System.out.println(Thread.currentThread().getName()+"...consumer........"+this.name);//Consumption of roast duck 1
		flag = false;
		notifyAll();//Inform the producer to produce roast duck
	}
}


So far, the multi consumer and multi producer mode is also completed, but the above is implemented with the synchronized keyword, and the solution to the lock object is the same. Just change the if judgment in the resource class of single consumer and single producer to the while judgment, and the code will not be pasted. However, we will introduce a more effective lock object solution. We are going to use two sets of Condition objects (also known as monitors) to implement the wait / notification mechanism, that is, obtain two sets of monitors through the existing lock, one to monitor the producer and the other to monitor the consumer. With the previous analysis, here we will go directly to the code:

package com.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @decrition Two sets of monitors are obtained through the existing lock, one to monitor the producer and the other to monitor the consumer.
 */
public class ResourceBy2Condition {
	private String name;
	private int count = 1;
	private boolean flag = false;
	
    //Create a lock object.
	Lock lock = new ReentrantLock();
	
	//Two sets of monitors are obtained through the existing lock, one to monitor the producer and the other to monitor the consumer.
	Condition producer_con = lock.newCondition();
	Condition consumer_con = lock.newCondition();
	
	/**
	 * production
	 * @param name
	 */
	public  void product(String name)
	{
		lock.lock();
		try
		{
			while(flag){
				try{producer_con.await();}catch(InterruptedException e){}
			}
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...Producer 5.0..."+this.name);
			flag = true;
//			notifyAll();
//			con.signalAll();
			consumer_con.signal();//Wake up consumer thread directly
		}
		finally
		{
			lock.unlock();
		}
	}
	
	/**
	 * consumption
	 */
	public  void consume()
	{
		lock.lock();
		try
		{
			while(!flag){
				try{consumer_con.await();}catch(InterruptedException e){}
			}
			System.out.println(Thread.currentThread().getName()+"...consumer.5.0......."+this.name);//Consumption of roast duck 1
			flag = false;
//			notifyAll();
//			con.signalAll();
			producer_con.signal();//Wake up production thread directly
		}
		finally
		{
			lock.unlock();
		}
	}
}

As you can see from the code, we created producer_con and consumer_con two condition objects are used to listen to the producer thread and consumer thread respectively. In the product() method, after we get the lock, if the flag is true at this time, that is, there are still roast ducks not consumed at this time, so the production thread needs to wait, so we call the monitor producer of the production thread_ The await() method of con enters the blocking waiting pool; However, if the flag is false at this time, it means that the roast duck has been consumed and the production thread needs to produce the roast duck. Then the production thread will produce the roast duck and pass the monitor consumer of the consumption thread_ The signal() method of con informs the consuming thread to consume the roast duck. The same is true for the consume() method. There is no more analysis here. We can find that this method is more efficient and convenient than our previous synchronized synchronization method or single monitor lock object. Previously, notifyAll() and signalAll() methods were used to wake up the threads in the pool, and then let the threads in the pool enter the contention queue to seize CPU resources, This not only wakes up irrelevant threads, but also makes all threads enter the competition queue. Finally, we use two kinds of listeners to listen to producer threads and consumer threads respectively. This way just solves the problem of the first two methods. Each time we wake up, we only wake up producer threads or consumer threads, not both at the same time, Isn't it more efficient to execute the program? Well, the multi producer and multi consumer model has been analyzed.

5. Thread deadlock

Now let's talk about thread deadlock. From the above analysis, we know that lock is a very useful tool, which is used in many scenarios, because it is very simple to use and easy to understand. But at the same time, it will also bring some unnecessary trouble, that is, it may cause deadlock. Once a deadlock occurs, the system function will be unavailable. Let's analyze it through an example. This example will cause deadlock, making thread t1 and thread t2 wait for each other to release the lock.

package com.test;
/**
 * @decrition Deadlock example
 */
public class DeadLockDemo {
	
	private static String A="A";
	private static String B="B";
			
	public static void main(String[] args) {
		DeadLockDemo deadLock=new DeadLockDemo();
		while(true){
			deadLock.deadLock();
		}
	}
	
	private void deadLock(){
		Thread t1=new Thread(new Runnable(){
			@SuppressWarnings("static-access")
			@Override
			public void run() {
				synchronized (A) {
					try {
						Thread.currentThread().sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				synchronized(B){
					System.out.println("1");
				}
			}
		});
		
		Thread t2 =new Thread(new Runnable() {	
			@Override
			public void run() {
				synchronized (B) {
					synchronized (A) {
						System.out.println("2");
					}
				}
			}
		});
		
		//Start thread
		t1.start();
		t2.start();
	}	
}

Synchronous nesting is a common scenario for deadlock generation. From the above code, we can see that when t1 thread gets lock a, it sleeps for 2 seconds. At this time, thread t2 just gets lock B, and then needs to obtain lock A. but at this time, lock a is just held by t1 thread, so we can only wait for t1 thread to release lock a, but unfortunately, it is required to obtain lock B in t1 thread, At this time, the b lock is held by the t2 thread. As a result, the t1 thread obtains the lock a and waits for the t2 thread to release the lock B, while the t2 thread obtains the lock B and waits for the t1 thread to release the lock A. waiting for each other also causes the thread deadlock problem. Although we generally don't write such code as above in reality, we may encounter such problems in some more complex scenarios. For example, after t1 takes the lock, because some exceptions do not release the lock (dead loop), or t1 gets a database lock, throws an exception when releasing the lock, and does not release it, Therefore, we should consider deadlock when writing code, so as to effectively prevent the occurrence of deadlock programs. Here are some common methods to avoid deadlock:

1. Prevent a thread from acquiring multiple locks at the same time.
2. Avoid occupying multiple resources in one resource, and try to ensure that each lock occupies only one resource.
3. Try to use timing lock, and use tryLock(timeout) instead of internal lock mechanism.
4. For database locks, locking and unlocking must be in a database connection, otherwise unlocking failure will occur.
5. Avoid synchronous nesting

6. Thread.join()

If A thread A executes thread join() statement, which means that the current thread A waits for the thread to terminate before starting from thread join() returns. In addition to the join () method, thread also provides two methods with timeout characteristics: join(long millis) and join(long millis,int nanos). These two timeout methods mean that if the thread does not terminate within A given timeout, it will be returned from the timeout method. The following is an example. Create 10 threads, numbered 0 ~ 9. Each thread calls the join () method of one thread, that is, thread 0 ends, thread 1 can return from the join () method, and 0 needs to wait for the end of the main thread.

package com.test;
/**
  * @decrition join case
 */
public class JoinDemo {
	
	public static void main(String[] args) {
		Thread previous = Thread.currentThread();
		for(int i=0;i<10;i++){
			//Each thread has a reference before it. You need to wait for the previous thread to terminate before you can return from the wait
			Thread thread=new Thread(new Domino(previous),String.valueOf(i));
			thread.start();
			previous=thread;
		}
		System.out.println(Thread.currentThread().getName()+" Thread end");
	}
}
class Domino implements Runnable{
	private Thread thread;
	public Domino(Thread thread){
		this.thread=thread;
	}
	
	@Override
	public void run() {
		try {
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName()+" Thread end");
	}
	
}

Topics: Java Multithreading