Understand the Java thread pool execution principle & core parameters in one article


Preface

During recent interviews, I have often been asked questions about multi-threading, especially thread pools, because this is also a commonly used way to create threads at work. This blog is to record my own implementation principles of thread pools and how to create thread pools for execution. An understanding of the task and easy review later


1. Why use a thread pool to create threads?

I summarized two reasons:

  • 1. Every time you create a thread, you need to occupy a certain amount of memory space. If you create threads indefinitely, it may waste memory space. In severe cases, it may cause memory overflow.
  • 2. Our CPU resources are limited. One CPU can only process one thread at the same time. If a large number of requests come in and a large number of threads are created, many of which do not have CPU execution rights, then these threads will have to wait. This will cause a large number of switching between threads and will also lead to slower performance.

2. Core parameters of thread pool (key points)

Insert image description here
Taking ThreadPoolExecutor as an example, we can see that its constructor has 7 core parameters. Next, I will give a brief explanation of them.

1. Number of core threads

  • That is to say, after a task comes in, the thread pool first creates a core thread to execute the task. For example, if two tasks come in, if the number of core threads is defined to be 2, 2 core threads will be created to execute the task. If the task comes in again, it will Enter blocking queue

2. Maximum number of threads

  • Maximum number of threads = number of core threads + number of emergency threads. If the number of core threads is 2 and the maximum number of threads is 3, then the number of emergency threads is 1

3. Survival time of emergency thread

  • This means that if there are no tasks within the survival time of the emergency thread, the emergency thread will be released, but the core thread will not be released.

4. Time unit of emergency thread

  • It is a TimeUnit enumeration class, which defines several time units: NANOSECONDS (nanoseconds), MICROSECONDS (microseconds), MILLISECONDS (milliseconds), SECONDS (seconds), MINUTES (minutes), HOURS (hours), DAYS (days) )

5. Task queue

  • Task blocking queue. When a new task comes in, if there is no idle core thread to execute at this time, the task will first enter the task blocking queue to wait.

6. Thread factory

  • Generally used to define the name of a thread

7. Task rejection strategy

  • The blocking queue is full, and there are no idle rescue threads. If a new task enters at this time, the new task will be processed according to the task rejection strategy. There are four task rejection strategies:
  • 1.AbortPolicy: Throw an exception directly
  • 2.CallerRunsPolicy: Use the thread where the caller is located to perform tasks, usually the main thread
  • 3.DiscardOldestPolicy: Discard the frontmost task in the blocking queue and execute the current task
  • 4.DiscardPolicy: Discard the task directly

3. Execution Principle of Thread Pool

In layman's terms, when a new task is submitted, it is judged whether the number of core threads is full. If not, a core thread is created to execute the task. If the number of core threads is full, the new task is added to the blocking queue. If the blocking queue is also If it is full, it will be judged whether the current number of threads is less than the maximum number of threads. If it is less than the maximum number of threads, an emergency thread will be created to perform the task. If the current number of threads is equal to the maximum number of threads, it means that there are no idle threads to perform the task, and the only option is to reject the new task. It will be processed according to the rejection policy.
Insert image description here

4. A small case

Detailed code:


```java
public class TestThreadPoolExecutor {
    
    

    static class MyTask implements Runnable {
    
    
        private final String name;
        private final long duration;

        public MyTask(String name) {
    
    
            this(name, 0);
        }

        public MyTask(String name, long duration) {
    
    
            this.name = name;
            this.duration = duration;
        }

        @Override
        public void run() {
    
    
            try {
    
    
                System.out.println("MyThread...running..." + this);
                Thread.sleep(duration);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

        @Override
        public String toString() {
    
    
            return "MyTask(" + name + ")";
        }
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        AtomicInteger c = new AtomicInteger(1);
        ArrayBlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);

        LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue();
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                2,
                3,
                0,
                TimeUnit.MILLISECONDS,
                queue,
                r -> new Thread(r, "myThread" + c.getAndIncrement()),
                new ThreadPoolExecutor.AbortPolicy());
        showState(queue, threadPool);
        threadPool.submit(new MyTask("1", 3600000));
        showState(queue, threadPool);
        threadPool.submit(new MyTask("2", 3600000));
        showState(queue, threadPool);
//        threadPool.submit(new MyTask("3"));
//        showState(queue, threadPool);
//        threadPool.submit(new MyTask("4"));
//        showState(queue, threadPool);
//        threadPool.submit(new MyTask("5",3600000));
//        showState(queue, threadPool);
//        threadPool.submit(new MyTask("6"));
//        showState(queue, threadPool);
    }

    private static void showState(ArrayBlockingQueue<Runnable> queue, ThreadPoolExecutor threadPool) {
    
    
        try {
    
    
            Thread.sleep(300);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        List<Object> tasks = new ArrayList<>();
        for (Runnable runnable : queue) {
    
    
            try {
    
    
                Field callable = FutureTask.class.getDeclaredField("callable");
                callable.setAccessible(true);
                Object adapter = callable.get(runnable);
                Class<?> clazz = Class.forName("java.util.concurrent.Executors$RunnableAdapter");
                Field task = clazz.getDeclaredField("task");
                task.setAccessible(true);
                Object o = task.get(adapter);
                tasks.add(o);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
        System.out.println("pool size:" + threadPool.getPoolSize() + ", " + tasks);
    }
}

In the above code, we define a static inner class to create tasks, and define the thread pool in the main method, as well as some of its core parameters. For example, the number of core threads defined is 2, and the maximum number of threads is 3, then The number of emergency threads is 1, the survival time of the emergency thread is 0ms, the task blocking queue is ArrayBlockingQueue, the thread factory defines the name of the thread, and the task rejection policy defines the AbortPolicy. When a new task comes, if the current number of threads is equal to the maximum If the thread and the task queue are full at the same time, the rejection policy will be triggered.
When we only submit two tasks, two core threads will be created to execute the tasks. At this time, the blocking queue is empty. The running results are as follows: When
Insert image description here
we submit three tasks, the third task will enter the blocking queue. :
Insert image description here
Insert image description here
When we submit four tasks, the third and fourth tasks will enter the blocking queue.
Insert image description here
Insert image description here
If we submit 5 tasks, since the capacity of the blocking queue we initially defined is 2, then the fifth task will create an emergency thread. implement
Insert image description here

Insert image description here
When we submit the sixth task, because the blocking queue is full and the current number of threads has reached the maximum number of threads, the task rejection policy will be triggered and an exception will be thrown:
Insert image description here
Insert image description here

Summarize

This blog briefly talks about the core parameters and execution principles of the thread pool, and uses a small case to illustrate one of its workflows. It will continue to update the knowledge points of the thread pool later, such as what blocking queues are and their internal principles. What.

Guess you like

Origin blog.csdn.net/qq_40187702/article/details/131619775