01. Preface
When using the thread pool, the deadlock problem is ignored, but as long as the code is written in "six", it is impossible
Article Code and some philosophy drawn from the thread pool improper use will deadlock
02, what is deadlock
Deadlock refers to a blocking phenomenon caused by competition for resources or due to communication between two or more processes in the execution process. If there is no external force, they will not be able to advance.
At this time, the system is said to be in a deadlock state or the system has a deadlock. These processes that are always waiting for each other are called deadlock processes.
Deadlock definition source: Deadlock-Baidu Encyclopedia
To put it simply: a group of threads competing for resources wait for each other, causing "permanent" blocking.
Let's understand it briefly through a Demo
public class DeadlockTest {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
Object o2 = new Object();
new Thread(() -> {
synchronized (o1) {
try {
System.out.println(String.format(" >>> %s 获取 o1 锁 ", Thread.currentThread().getName()));
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(String.format(" >>> %s 获取 o2 锁 ", Thread.currentThread().getName()));
}
}
}, "线程一").start();
Thread.sleep(10);
new Thread(() -> {
synchronized (o2) {
try {
System.out.println(String.format(" >>> %s 获取 o2 锁 ", Thread.currentThread().getName()));
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println(String.format(" >>> %s 获取 o1 锁 ", Thread.currentThread().getName()));
}
}
}, "线程二").start();
}
/**
* >>> 线程一 获取 o1 锁
* >>> 线程二 获取 o2 锁
*/
}
In the program, thread 1 first obtains the o1 object lock, then sleeps for 50ms, and then wakes up to obtain the o2 object lock
Thread 2 first obtains the o2 object lock, then sleeps for 50ms, and obtains the o1 object lock after waking up
In this way, the deadlock is very subtle, and the details of the deadlock are not described here.
03, the conditions of thread pool deadlock
Use code to simulate the causes and consequences of thread pool deadlock. Interested friends can pull the code to run locally
The scenario represented by this code is as follows:
1. Create a JDK thread pool, and add a thread to print the thread pool information after three seconds
2. Call the innerFutureAndOutFuture method in a loop
3. The running process of innerFutureAndOutFuture is to submit ten Callable to run
4. However, the Callable task is additionally dependent on the Callable submission, and the execution result needs to be obtained
Ideally, the while loop will continue to execute and increment loop
It is recommended that the computer watch the code, or paste it into the editor to run
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class HungryDeadLockTest {
private static ThreadPoolExecutor executor;
public static void main(String[] args) throws InterruptedException, ExecutionException {
TimeUnit unit = TimeUnit.HOURS;
BlockingQueue workQueue = new LinkedBlockingQueue();
executor = new ThreadPoolExecutor(5, 5, 1000, unit, workQueue);
new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executor);
}).start();
int loop = 0;
while (true) {
System.out.println("loop start. loop = " + (loop));
innerFutureAndOutFuture();
System.out.println("loop end. loop = " + (loop++));
Thread.sleep(10);
}
}
public static void innerFutureAndOutFuture() throws ExecutionException, InterruptedException {
Callable<String> innerCallable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(100);
return "inner callable";
}
};
Callable<String> outerCallable = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(10);
Future<String> innerFuture = executor.submit(innerCallable);
String innerResult = innerFuture.get();
Thread.sleep(10);
return "outer callable. inner result = " + innerResult;
}
};
List<Future<String>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
System.out.println("submit : " + i);
Future<String> outerFuture = executor.submit(outerCallable);
futures.add(outerFuture);
}
for (int i = 0; i < 10; i++) {
String outerResult = futures.get(i).get();
System.out.println(outerResult + ":" + i);
}
}
}
Looking at the above code, let's first sort out the state of the thread pool after submitting the task
Let's first come to the three core parameters of the thread pool
- Core threads: 5
- Maximum number of threads: 5
- Blocking queue: unbounded queue
1. Add outerCallable to execute in the for loop, add ten tasks
At this time, the five tasks of the core thread count are full, and the remaining five tasks will be added to the unbounded queue
2. The innerCallable task will be created in outerCallable and submitted to the same thread pool
Because innerCallable is executed in the call method of outerCallable, it is equivalent to executing ten tasks in the thread pool.
3. Because it sleeps for 10 ms before the outerCallable task is executed, it can ensure that all outerCallables are executed
Let's first look at the results of the program
/**
* loop start. loop = 0
* submit : 0
* submit : 1
* submit : 2
* submit : 3
* submit : 4
* submit : 5
* submit : 6
* submit : 7
* submit : 8
* submit : 9
* java.util.concurrent.ThreadPoolExecutor@5b6dc686[Running, pool size = 5, active threads = 5, queued tasks = 10, completed tasks = 0]
*/
Combining the operation results and the operation flowchart, let's analyze and ask some questions
1. OuterCallable and innerCallable are submitted ten times each, why there are only ten tasks in queued tasks
Think about it: Our core threads are five. According to the code, 20 tasks will be submitted. In theory, there should be 15 tasks in the blocking queue?
We executed the outerCallable submission ten times in the for loop, and the outerCallable executed a 10ms sleep inside
At this time, the state of the thread pool is: five core threads run, five blocked queue tasks, and the outerCallable running by the five core threads wakes up from 10 ms sleep
At this time, the innerCallable will be submitted through the outerCallable occupying five core threads.
Because only five core thread, so blocking queue 10 tasks are five outerCallable and five innerCallable
2. Why does the thread pool deadlock?
Through the above explanation, the answer has come out
The outerCallable running by five core threads is waiting for the innerCallable in the blocking queue to return results
Reached the standard for deadlock generation, resources are waiting for each other to execute
04, how to prevent thread pool deadlock
The core point is: Don't wait in the thread pool for another task executed in the pool
- Do not perform asynchronous tasks in a synchronous method in the thread pool
- Don't block and wait for asynchronous methods in asynchronous methods
Of course, the deadlock problem of the above code can also be solved by increasing the number of threads in the thread pool.
You can change the number of core threads to 40, and the maximum number of threads to 50, and see if you can still deadlock
This kind of solution can be solved, but it is better not to have
Because the parameter setting of the thread pool needs to be rationalized, it cannot be set to solve certain types of problems
05. Summary at the end of the article
Here is a small example to describe the possibility of a deadlock in the thread pool
Hope that everyone can avoid the possibility of thread pool deadlock in the project through the above description