Java thread pool -- process / principle / Mechanism -- use step / size setting / saturation policy / rejection policy / exception handling

Original website: Java thread pool -- process / principle / Mechanism -- use step / size setting / saturation policy / rejection policy / exception handling_ CSDN blog

brief introduction

Why use thread pools

        When we use threads, we create a thread, which is very easy to implement, but there will be a problem: if there are a large number of concurrent threads and each thread executes a task for a short time, the system efficiency will be greatly reduced because it takes time to create and destroy threads frequently.
        So is there a way to make threads reusable, that is, after executing a task, they can continue to execute other tasks without being destroyed?
        In Java, this effect can be achieved through thread pool. Before JDK5, we must manually implement our own thread pool. Starting from JDK5, Java built-in supports thread pool.

Network requests usually take two forms

        The first one: the request is infrequent, and will remain for a considerable period of time to read or write data after each connection, and finally disconnect, such as file download, network streaming media, etc.
        The other is that the request is frequent, but the connection is disconnected after reading / writing a small amount of data. Considering the concurrency of the service, if the service starts a thread for each request after it arrives, it may cause a great waste of service resources, especially in the second case.

        Generally, creating a thread takes a certain amount of time. Set this time as T1 and the time for reading / writing the service after connection as T2. When T1 > > T2, we should consider a strategy or mechanism to control so that the service can complete the second request mode with low power consumption.

        Usually, we can use thread pool to solve this problem. First, when the service is started, we can start several threads and use a container (such as thread pool) to manage these threads. When a request arrives, you can take a thread from the pool to execute the task (usually the response to the request). After the task is completed, you can put the thread into the pool for standby; If a request arrives and there are no idle threads in the pool, the request needs to be queued. Finally, destroy the pool when the service is shut down.

Thread pool role

  1. Reduce resource consumption. Reduce the consumption caused by thread creation and destruction by reusing the created threads.
  2. Improve response speed. When the task arrives, the task can be executed immediately without waiting for the thread to be created.
  3. Improve thread manageability. Threads are scarce resources. If they are created without restrictions, they will not only consume system resources, but also reduce the stability of the system. Using thread pool can be used for unified allocation, tuning and monitoring.

Disadvantages of thread pool

  1. Priority cannot be set for tasks in a thread pool.
  2. It is suitable for tasks with short life cycle, not for long and large tasks.
  3. You cannot identify each state of a thread, such as starting a thread or terminating a thread.
  4. Only one thread pool can be allowed for any given application domain.
  5. All threads in the thread pool are in a multi-threaded unit. If you want to put threads in a single threaded unit, the thread pool will be discarded.

Composition structure of thread pool

A thread pool consists of four basic parts

  1. Thread pool: used to create and manage thread pools, including creating, destroying and adding new tasks;
  2. PoolWorker: threads in the thread pool are in a waiting state when there are no tasks, and can execute tasks circularly;
  3. Task interface (task): each task must implement an interface, which is used to provide the work thread to schedule the execution of the task, and specify the entry of the task, the ending work after execution and the execution status of the task;
  4. Task queue: used to store unprocessed tasks. It provides a caching mechanism.

Inheritance architecture of thread pool

        Executor is a top-level interface in which only one method execute(Runnable) is declared , the return value is void and the parameter is Runnable. It can be understood from the literal meaning that it is used to execute the tasks passed in. Then, the ExecutorService interface inherits the executor interface and declares some methods: submit, invokeAll, invokeAny, shutDown, etc.; the abstraction class AbstractExecutorService implements the ExecutorService interface, which basically implements the functions in ExecutorService All methods declared; then ThreadPoolExecutor inherits the class AbstractExecutorService.

Important classes:

ExecutorService

Real thread pool interface.

ScheduledExecutorService

Similar to Timer/TimerTask, it can solve problems that require repeated task execution.

ThreadPoolExecutor

The default implementation of ExecutorService.

ScheduledThreadPoolExecutor

It inherits the implementation of the ScheduledExecutorService interface of ThreadPoolExecutor and the class implementation of periodic task scheduling.

Thread pool status

Five statuses of thread pool: Running, ShutDown, Stop, Tidying and Terminated.

state

explain

State switching

RUNNING

When the thread pool is in the RUNNING state, it can receive new tasks and process added tasks. This statement will be called when creating:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

The initialization state of the thread pool is RUNNING. In other words, once the thread pool is created, it is in the RUNNING state, and the number of tasks in the thread pool is 0!

SHUTDOWN

When the thread pool is in SHUTDOWN state, it does not receive new tasks, but can process added tasks.

When calling the shutdown() interface of the thread pool, the thread pool is run - > shutdown.

STOP

When the thread pool is in the STOP state, it will not receive new tasks, will not process added tasks, and will interrupt the tasks being processed.

When calling the shutdown now() interface of the thread pool, the thread pool is controlled by (running or shutdown) - > stop.

TIDYING

When all tasks have been terminated and the "number of tasks" recorded in ctl is 0, the thread pool will change to the TIDYING state. When the thread pool changes to the TIDYING state, the hook function terminated() will be executed. Terminated() is empty in the ThreadPoolExecutor class. If the user wants to process when the thread pool changes to TIDYING, it can be implemented by overloading the terminated() function.

When the thread pool is in SHUTDOWN state, the blocking queue is empty and the tasks executed in the thread pool are also empty, it will be determined by SHUTDOWN - > tidying.

When the thread pool is in the STOP state and the tasks executed in the thread pool are empty, it will be stopped - > tidying.

TERMINATED

When the thread pool terminates completely, it becomes TERMINATED.

When the thread pool is in the TIDYING state, it will be terminated by TIDYING - > terminated after executing terminated().

Number (size) setting

Task classification  

According to the amount of cpu and io resources required by the task, it can be divided into:

  • CPU intensive tasks:
    • It mainly performs computing tasks. The response time is very fast and the cpu is running all the time. This kind of task has high cpu utilization.
    • Too large thread pool size is unfavorable to program performance, but at least it should not be lower than the number of processor cores. Because when there are multiple tasks in the ready state, the processor core needs to make frequent context switching between processes, and this switching consumes a lot of program performance.
  • IO intensive tasks
    • It is mainly for IO operations, which take a long time to execute. At this time, the cpu is idle, resulting in low cpu utilization.
    • When a task performs IO operation, its thread will be blocked, so the processor can immediately switch the context to deal with other ready threads. If we only have as many threads as the processor's available cores, even the tasks to be executed cannot be processed, because we can't get more threads for the processor to schedule.  

Method for distinguishing CPU intensive tasks from IO intensive tasks

        If tasks are blocked for less than execution time, that is, these tasks are computationally intensive, the number of threads required by the program will be reduced, but at least it should not be less than the number of cores of the processor.
        If the blocking time of a task is longer than the execution time, that is, the task is IO intensive, we need to create a number of threads several times larger than the number of processor cores. For example, if a task is blocked 50% of the time, the number of threads required by the program is twice the number of cores available to the processor.

Common thread pool size settings

  • CPU intensive: number of core threads = number of CPU cores  + one
  • IO intensive: number of core threads = number of CPU cores * 2 + 1

The number of CPU cores can be obtained by this method: Runtime.getRuntime().availableProcessors()

For computing intensive tasks, a system with N processors usually uses a thread pool of N+1 threads to obtain the optimal utilization (the computing intensive thread is suspended due to a page error or other reasons at a certain time, and there is just an "additional" thread, which can ensure that the CPU cycle will not interrupt the work in this case).

  Calculation formula

N = number of CPUs

U = expected CPU usage, between 0-1

f: Blocking coefficient (percentage of blocking time in total time. Total time: blocking time + execution time)

Thread pool size = N * U / (1 - f)         // A completely blocked task is doomed to die. There is no need to worry that the blocking coefficient will reach 1.

For example, the number of CPU cores is 4, and the expected CPU utilization is 100%. Suppose the waiting time is 4 seconds and the calculation time is 1 second. Then my optimal pool size is:

4 * 100% / (1 - 4/5) = 20

Thread pool usage steps

step

1. Create a thread pool object to control how many Thread objects to create.

2. Implementation thread:
  Method 1: create a new class to implement the Runnable or Callable interface.
  Method 2: pass values directly with lambda expressions. For example:

public class Demo {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);
        executor.execute(Demo::myRun);
    }

    public static void myRun() {
        System.out.println("hello world");
    }
}

3. The submitting thread can call any of the following methods

void execute(Runnable command);
Future<?> submit(Runnable task)
<T> Future<T> submit(Callable<T> task)
<T> Future<T> submit(Runnable task, T result)

4. End thread pool.

submit and execute

termsubmitexecute
explain

Is a method in ExecutorService.

To submit a task.

Is the method of the Executor interface.

Execute the given command at some future time. The command may be executed in a new thread, a pooled thread, or a calling thread, depending on the implementation of the Executor.

Method prototype

 <T> Future<T> submit(Callable<T> task)

       // Submit a return value task for execution

        // Returns a Future that represents the pending results of the task.
Future<?> submit(Runnable task)

        // Submit a Runnable task for execution

        // Returns a Future that represents the task.
<T> Future<T> submit(Runnable task, T result)

        // Submit a Runnable task for execution

        // Returns a Future that represents the task.

void execute(Runnable command)
Return valueThe return value is the future object    Execution results can be obtainedno return value

Future

    Future represents the result of asynchronous calculation. It provides a method to check whether the calculation is completed, so as to wait for the completion of the calculation and obtain the results of the calculation. After the calculation is completed, you can only use the get method to obtain the results. If necessary, you can block this method before the calculation is completed. Cancel is performed by the cancel method. Other methods are also provided to determine whether the task is completed normally or cancelled. Once the calculation is completed, you cannot cancel the calculation.
     If you use future for cancellability but do not provide available results, you can declare future <? > Form type and return null as the result of the underlying task.     Future is to cancel the execution result of a specific Runnable or Callable task, query whether it is completed, and obtain the result. If necessary, you can get the execution result through the get method, which will block until the task returns the result.  
     In other words, Future provides three functions:

    -- Judge whether the task is completed;
    -- Ability to interrupt tasks;
    -- Be able to obtain task execution results.

  • boolean cancel(boolean mayInterruptIfRunning) attempts to cancel the execution of this task.
  • V get() if necessary, wait for the calculation to complete, and then get its results.
  • V get(long timeout, TimeUnit unit) if necessary, wait at most for the time given to complete the calculation to obtain its result (if the result is available).
  • boolean isCancelled() returns true if the task is cancelled before it completes normally.
  • boolean isDone() returns true if the task has been completed.

Close thread pool

    void   shutdown() starts a sequential shutdown, waiting for the execution of previously submitted tasks to complete, but does not accept new tasks.
     List < runnable > shutdownnow() attempts to immediately stop all active tasks being executed, pause the processing of waiting tasks, and return to the list of waiting tasks.

Thread pool process

flow chart

  1. Submit task
  2. The thread pool determines whether the threads in the core thread pool are full (all executing tasks).
    1. If not: check whether the number of threads reaches the specified size of the core thread pool
      1. If not: create a new thread to execute the task.
      2. If yes: use idle threads to perform tasks
    2. If yes, proceed to the next process.
  3. The thread pool determines whether the work queue is full.
    1. If not, the newly submitted task is stored in the work queue.
    2. If the work queue is full, proceed to the next process.
      1. The queue is shown in: Multithreading -- BlockingQueue_feiying0canglang's blog - CSDN blog
  4. The thread pool determines whether all threads (maximum number of threads) in the thread pool are full (all executing tasks).
    1. If not: check whether the number of threads reaches the specified maximum thread pool size
      1. If not: create a new thread to execute the task.
      2. If yes: use idle threads to perform tasks
    2. If yes, it is left to the saturation strategy to handle this task. (see below for saturation strategy)

Saturation policy (reject Policy)

Saturation strategy

explain

ThreadPoolExecutor.AbortPolicy

The default blocking policy for the Java thread pool.

This task is not executed, and a runtime exception (unchecked exception RejectedExecutionException) is thrown directly. Remember that ThreadPoolExecutor.execute needs to try catch, otherwise the program will exit directly.

ThreadPoolExecutor.DiscardPolicy

Directly discard, task not executed, empty method.

ThreadPoolExecutor.DiscardOldestPolicy

Delete the oldest task (the task in the head) from the queue and execute the task again.

ThreadPoolExecutor.CallerRunsPolicy

Having the thread calling the execute method execute this command will block the entry.

In order to adjust the mechanism, it neither discards the task nor throws an exception, but returns some tasks to the caller and lets the thread where the caller is located execute them.

User defined rejection policy (most commonly used)

Implement RejectedExecutionHandler and define the policy pattern yourself

routine

package concurrency.pool;
 
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
/**
 * Created by li on 2016/7/2.
 */
public class SaturationPolicy {
 
    /**
     * Performance under different saturation strategies when the thread pool work queue is full
     * @param handler Thread pool work queue saturation policy
     */
    public static void policy(RejectedExecutionHandler handler){
        //There are 2 basic threads, the maximum number of threads is 3, and the work queue capacity is 5
        ThreadPoolExecutor exec = new ThreadPoolExecutor(2,3,0, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<>(5));
        if (handler != null){
            exec.setRejectedExecutionHandler(handler);//Set saturation policy
        }
        for (int i = 0; i < 10; i++) {
            exec.submit(new Task());//Submit task
        }
        exec.shutdown();
    }
 
    //Custom task
    static class Task implements Runnable {
        private static int count = 0;
        private int id = 0;//Task ID
        public Task() {
            id = ++count;
        }
        @Override
        public  void run() {
            try {
                TimeUnit.SECONDS.sleep(3);//Sleep for 3 seconds
            } catch (InterruptedException e) {
                System.err.println("Thread interrupted" + e.getMessage());
            }
            System.out.println(" Task:" + id + "\t Worker thread: "+ Thread.currentThread().getName() + " completion of enforcement");
        }
    }

    public static void main(String[] args) {
//        policy(new ThreadPoolExecutor.AbortPolicy());
//        policy((new ThreadPoolExecutor.CallerRunsPolicy()));
//        policy(new ThreadPoolExecutor.DiscardPolicy());
//        policy(new ThreadPoolExecutor.DiscardOldestPolicy());
    }
}

1.Abort policy: default policy.

An unchecked exception RejectedExecutionException is thrown directly when a new task is submitted, which can be caught by the caller.

  Add the following code to the main function:

policy(new ThreadPoolExecutor.AbortPolicy());

Operation results:  

        The program threw a RejectedExecutionException and ran a total of 8 tasks (the thread pool can run 3 tasks at the beginning, and 5 queues are stored in the work queue). When the work queue is full, an exception is thrown directly, and the JVM never exits (I don't know why now). We can see that the threads executing tasks are all threads in the thread pool.

2.Discard strategy. The newly submitted task was abandoned.

Run in main function

policy(new ThreadPoolExecutor.DiscardPolicy());

  Operation results:

The above results show that no exception is thrown, the two new tasks submitted later are abandoned, only the first 8 (3 + 5) tasks are processed, and the JVM exits.

3. Discard old policy. The queue is the task of the "queue head", and then try to submit a new task. (not suitable for the scenario where the work queue is a priority queue)

Run the following method in the main function

policy(new ThreadPoolExecutor.DiscardOldestPolicy());

Running results: a total of 8 tasks are run. The program ends. Task 9 and task 10 added later are executed, while task 3 and task 4 are discarded.

4.CallerRuns policy

Run in main function:

 policy((new ThreadPoolExecutor.CallerRunsPolicy()));

Operation results

All tasks are run, and 2 (10 - 3 - 5) tasks are successfully executed in the main thread, and 8 tasks are executed by threads in the thread pool.

ThreadPoolExecutor

brief introduction

ThreadPoolExecutor structure diagram

  1. If the number of threads currently running is less than corePoolSize, a new thread is created to execute the task (note that this step requires obtaining a global lock).
  2. If the threads running are equal to or more than corePoolSize, the task is added to BlockingQueue.
  3. If the task cannot be added to the BlockingQueue (the queue is full), create a new thread to process the task (note that you need to obtain a global lock to perform this step).
  4. If creating a new thread will cause the currently running thread to exceed the maximumPoolSize, the task will be rejected and the RejectedExecutionHandler.rejectedExecution() method will be called.

Method parameters

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
  1. corePoolSize: the number of core threads in the thread pool: even if there are no tasks in the thread pool, there will be corePoolSize threads waiting for tasks.
  2. maximumPoolSize: maximum number of threads. Exceeding this number triggers a reject policy.
  3. keepAliveTime: the lifetime of the thread. When the number of threads in the thread pool is greater than the corePoolSize, if no task can be executed after waiting for keepAliveTime, the thread exits.
  4. Unit: This is used to specify the unit of keepAliveTime, such as seconds: TimeUnit.SECONDS.
  5. workQueue: a blocking queue in which submitted tasks will be placed.
  6. threadFactory: thread factory. It is used to create threads. It is mainly used to name threads. The thread name of the default factory is pool-1-thread-3.
  7. handler: reject policy, which will be called when the thread pool is exhausted and the queue is full.

        In java doc, we are not encouraged to directly use ThreadPoolExecutor, but to use several static methods provided in executors class to create thread pool, such as Executors.newCachedThreadPool();    Executors.newSingleThreadExecutor(); Wait. However, in fact, many large companies explicitly require the method of creating ThreadPoolExecutor object to use thread pool, because it can make people clear the specific meaning of each parameter. The static methods provided in the executors class all call the method to create the ThreadPoolExecutor object, for example:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

Example 1: sleep task

code

package com.example;

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("Executing task " + taskNum);
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----------------------task " + taskNum + "completion of enforcement");
    }
}

public class Demo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10,
                200, TimeUnit.MILLISECONDS,
                new LinkedBlockingDeque<>(4));

        for (int i = 0; i < 14; i++) {
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("Number of threads in the thread pool:" + executor.getPoolSize() + ",Number of tasks waiting in the queue:" +
                    executor.getQueue().size() + ",Number of other tasks completed:" + executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}

Operation results

Connected to the target VM, address: '127.0.0.1:58452', transport: 'socket'
Executing task 0
 Number of threads in the thread pool: 1, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 2, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 3, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 1
 Number of threads in the thread pool: 4, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 2
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 3
 Executing task 4
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 1, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 2, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 3, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 6, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 7, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 9
 Executing task 10
 Number of threads in the thread pool: 8, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 9, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 11
 Executing task 12
 Number of threads in the thread pool: 10, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 13
-----------------------task 3 completion of enforcement
-----------------------task 2 completion of enforcement
-----------------------task 1 completion of enforcement
 Executing task 5
-----------------------task 4 completion of enforcement
 Executing task 7
 Executing task 8
-----------------------task 0 completion of enforcement
-----------------------task 11 completion of enforcement
-----------------------task 10 completion of enforcement
-----------------------task 9 completion of enforcement
 Executing task 6
-----------------------task 13 completion of enforcement
-----------------------task 12 completion of enforcement
-----------------------task 5 completion of enforcement
-----------------------task 8 completion of enforcement
-----------------------task 6 completion of enforcement
-----------------------task 7 completion of enforcement
Disconnected from the target VM, address: '127.0.0.1:58452', transport: 'socket'

Process finished with exit code 0

As can be seen from the implementation results:

  1. When the number of threads in the thread pool is greater than 5, the task is placed in the task cache queue. When the task cache queue is full, a new thread is created.
  2. After all threads are executed, the program can exit normally.

If the for loop is changed to 15 in the above program, a task rejection exception will be thrown. The log is as follows:

Connected to the target VM, address: '127.0.0.1:58469', transport: 'socket'
Executing task 0
 Number of threads in the thread pool: 1, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 2, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 3, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 1
 Executing task 2
 Number of threads in the thread pool: 4, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 3
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 4
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 1, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 2, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 3, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 6, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 7, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 9
 Number of threads in the thread pool: 8, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 10
 Number of threads in the thread pool: 9, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 11
 Number of threads in the thread pool: 10, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 12
 Executing task 13
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.example.MyTask@27ddd392 rejected from java.util.concurrent.ThreadPoolExecutor@19e1023e[Running, pool size = 10, active threads = 10, queued tasks = 4, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at com.example.Demo.main(Demo.java:34)
-----------------------task 4 completion of enforcement
-----------------------task 1 completion of enforcement
-----------------------task 9 completion of enforcement
-----------------------task 0 completion of enforcement
-----------------------task 13 completion of enforcement
-----------------------task 3 completion of enforcement
-----------------------task 2 completion of enforcement
-----------------------task 12 completion of enforcement
-----------------------task 11 completion of enforcement
-----------------------task 10 completion of enforcement
 Executing task 8
 Executing task 6
 Executing task 5
 Executing task 7
-----------------------task 7 completion of enforcement
-----------------------task 6 completion of enforcement
-----------------------task 5 completion of enforcement
-----------------------task 8 completion of enforcement

result:

  1. When executing the 15th thread, an exception is thrown directly, but it does not affect the operation of other processes
  2. The program cannot exit normally

Example 2: dead cycle task

The above sleep task is only modified as dead cycle, and others remain unchanged

package com.example;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

class MyTask implements Runnable {
    private int taskNum;

    public MyTask(int num) {
        this.taskNum = num;
    }

    @Override
    public void run() {
        System.out.println("Executing task " + taskNum);
        while (true) ;
    }
}

public class Demo {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10,
                200, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(4));

        for (int i = 0; i < 14; i++) {
            MyTask myTask = new MyTask(i);
            executor.execute(myTask);
            System.out.println("Number of threads in the thread pool:" + executor.getPoolSize() + ",Number of tasks waiting in the queue:" +
                    executor.getQueue().size() + ",Number of other tasks completed:" + executor.getCompletedTaskCount());
        }
        executor.shutdown();
    }
}

Execution result: (the program cannot exit)

Connected to the target VM, address: '127.0.0.1:60128', transport: 'socket'
Executing task 0
 Number of threads in the thread pool: 1, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 2, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 3, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Executing task 1
 Number of threads in the thread pool: 4, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 0, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 1, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 2, number of other tasks completed: 0
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 3, number of other tasks completed: 0
 Executing task 3
 Number of threads in the thread pool: 5, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 4
 Number of threads in the thread pool: 6, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 2
 Number of threads in the thread pool: 7, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 8, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 9, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Number of threads in the thread pool: 10, number of tasks waiting to be executed in the queue: 4, number of other tasks completed: 0
 Executing task 9
 Executing task 10
 Executing task 12
 Executing task 11
 Executing task 13

exception handling

brief introduction

brief introduction

        When a RuntimeException is thrown, the thread will terminate, and the main thread and other threads will not be affected at all, and the exception thrown by a thread will not be perceived at all (that is, the exception cannot be caught at all).

Example solution

package org.example.a;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(3);

        executorService.execute(()->{
            int i = 1/0;
        });


        executorService.execute(()->{
            System.out.println("Normal task");
        });
    }
}

  Operation results

Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    at org.example.a.Demo.lambda$main$0(Demo.java:11)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)
Normal task

Scheme for catching thread pool exceptions

How can I get the exception thrown by the task in the thread pool?

Scheme 1: try catch the whole task to catch the exceptions (it's too troublesome to add a try catch to each task, and the code is not good-looking)

package org.example.a;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        executorService.execute(() -> {
            try {
                int i = 1 / 0;
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        });

        executorService.execute(() -> {
            System.out.println("Normal task");
        });
    }
}

Operation results

/ by zero
Normal task

Run results after changing execute to submit

Normal task

  Scenario 2: set the UncaughtExceptionHandler of the thread

package org.example.a;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(1);

        Thread thread=new Thread(() -> {
            int i = 1 / 0;
        });

        thread.setDefaultUncaughtExceptionHandler((Thread t, Throwable e) -> {
            System.out.println("exceptionHandler: " + e.getMessage());
        });

        executorService.execute(thread);
    }
}

Operation results

exceptionHandler: / by zero

Scheme 3: specify the thread pool factory method and set the UncaughtExceptionHandler of its thread

package org.example.a;

import java.util.concurrent.*;

public class Demo {
    public static void main(String[] args) {
        //1. Implement your own thread pool factory
        ThreadFactory factory = (Runnable r) -> {
            //Create a thread
            Thread t = new Thread(r);
            //Set the default logic to implement exceptions in the UncaughtExceptionHandler object for the created thread
            t.setDefaultUncaughtExceptionHandler((Thread thread1, Throwable e) -> {
                System.out.println("ThreadFactory: exceptionHandler" + e.getMessage());
            });
            return t;
        };

        //2. Create a self-defined thread pool and use the self-defined thread factory
        ExecutorService service = new ThreadPoolExecutor(1, 1, 0, TimeUnit.MILLISECONDS,new LinkedBlockingQueue(10), factory);

        //3. Submit tasks
        service.execute(()->{
            int i=1/0;
        });
    }
}

results of enforcement

ThreadFactory: exceptionHandler/ by zero

Note: if you change execute to submit, there will be no output.

principle

Other web sites

Detailed explanation of thread pool exception handling_ HelloWorld CSDN blog_ Thread pool exception

What is the difference between execute and submit?

execute

        The task submitted by execute will be encapsulated into a Runable task, and then the Runable object will be encapsulated into a Worker. Finally, the runWoker method will be run in the Worker's run method, and our initial parameters will be adjusted. The task of the Runable task will be caught with try catch, and will be thrown out directly, Therefore, we see the exception information of our task in execute.

submit

        The task submitted by submit will be encapsulated into a Runable task, and then the Runable Object will be encapsulated into a FutureTask. The run method try catch all exceptions and set them to the outcome (Object type). The outcome can be obtained through FutureTask. Get.

        Therefore, when submitting a submit, if an exception occurs, no information will be thrown. In submit, there are no other methods except to get exceptions from the returned results.

        Therefore, it's better to use execute when you don't need to return results, so that if you miss exception capture, you won't lose exception information.

Tags: Java Multithreading thread pool

Posted on Thu, 14 Oct 2021 15:53:46 -0400 by kctigers23