[Sword refers to Offer] Starvation deadlock caused by improper use of JDK thread pool

Insert picture description here

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

Figure 1 Running process

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



Recommended reading

Quickly consume tasks without exceeding the maximum number of threads in the JDK thread pool

How does the JDK thread pool ensure that core threads are not destroyed

How to deal with thread execution exceptions in JDK thread pool

Use Java 8's new feature ParallelStream with caution

Guess you like

Origin blog.csdn.net/qq_37781649/article/details/108674706