[Thread pool] 7 ways to create a thread pool, explained in detail

1. What is a thread pool?

Thread Pool (ThreadPool) is a mechanism for managing and using threads based on pooling ideas. It stores multiple threads in a "pool" in advance. When a task occurs, it can avoid the performance overhead caused by recreating and destroying threads. It only needs to take out the corresponding thread from the "pool" to execute the corresponding task. .

池化思想在计算机的应用也比较广泛,比如以下这些:

  • Memory Pooling: Pre-apply for memory, improve the speed of memory application, and reduce memory fragmentation.
  • Connection Pooling: Pre-apply for database connections to increase the speed of applying for connections and reduce system overhead.
  • Object Pooling: Recycle objects to reduce the costly loss of resources during initialization and release.

线程池的优势主要体现在以下 4 点:

  • Reduce resource consumption: reuse created threads through pooling technology to reduce the loss caused by thread creation and destruction.
  • Improved responsiveness: When a task arrives, it executes immediately without waiting for thread creation.
  • Improve the manageability of threads: Threads are scarce resources. If they are created without limit, it will not only consume system resources, but also lead to unbalanced resource scheduling due to unreasonable distribution of threads and reduce system stability. Use the thread pool for unified allocation, tuning, and monitoring.
  • Provide more and more powerful functions: The thread pool is scalable, allowing developers to add more functions to it. For example, the delayed timing thread pool ScheduledThreadPoolExecutor allows tasks to be deferred or periodically executed.

同时阿里巴巴在其《Java开发手册》中也强制规定:线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

  • Description: The advantage of thread pool is to reduce the time spent on creating and destroying threads and the overhead of system resources, and solve the problem of insufficient resources.
  • If the thread pool is not used, it may cause the system to create a large number of threads of the same type, resulting in memory consumption or "over-switching".

After knowing what a thread pool is and why to use a thread pool, let's see how to use a thread pool.

2. Classification of thread pools

There are 7 ways to create a thread pool, but they can be generally divided into 2 categories:
insert image description here

  1. Executors.newFixedThreadPool: Create a fixed-size thread pool, which can control the number of concurrent threads, and the excess threads will wait in the queue;
  2. Executors.newCachedThreadPool: Create a cacheable thread pool. If the number of threads exceeds the processing requirements, the cache will be recycled after a period of time. If the number of threads is not enough, a new thread will be created;
  3. Executors.newSingleThreadExecutor: Create a thread pool with a single number of threads, which can guarantee the execution order of first-in-first-out;
  4. Executors.newScheduledThreadPool: Create a thread pool that can execute delayed tasks;
  5. Executors.newSingleThreadScheduledExecutor: Create a single-threaded thread pool that can execute delayed tasks;
  6. Executors.newWorkStealingPool: Create a thread pool for preemptive execution (task execution order is uncertain) [JDK 1.8 added].
  7. ThreadPoolExecutor: The most primitive way to create a thread pool, it contains 7 parameters for setting, which will be described in detail later.

Then let's look at the specific use of each thread pool creation.

3. The use of thread pool

FixedThreadPool
Create a thread pool with a fixed size, which can control the number of concurrent threads, and the excess threads will wait in the queue.

public static void fixedThreadPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    // 执行任务
    threadPool.execute(() -> {
    
    
        System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
    });
}

insert image description here
CachedThreadPool
Create a cacheable thread pool. If the number of threads exceeds the processing requirements, the cache will be recycled after a period of time. If the number of threads is not enough, new threads will be created.

public static void cachedThreadPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newCachedThreadPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        threadPool.execute(() -> {
    
    
            System.out.println("任务被执行,线程:" + Thread.currentThread().getName());
        });
    }
}

insert image description here
SingleThreadExecutor
Create a thread pool with a single number of threads, which can guarantee the execution order of first-in-first-out.

public static void singleThreadExecutor() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newSingleThreadExecutor();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + ":任务被执行");
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
            }
        });
    }
}

insert image description here
ScheduledThreadPool
Create a thread pool that can execute delayed tasks.

public static void scheduledThreadPool() {
    
    
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
    // 添加定时执行任务(1s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
    
    
        System.out.println("任务被执行,时间:" + new Date());
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
    }, 1, TimeUnit.SECONDS);
}

insert image description here
SingleThreadScheduledExecutor
Create a single-threaded thread pool that can execute delayed tasks.

public static void SingleThreadScheduledExecutor() {
    
    
    // 创建线程池
    ScheduledExecutorService threadPool = Executors.newSingleThreadScheduledExecutor();
    // 添加定时执行任务(2s 后执行)
    System.out.println("添加任务,时间:" + new Date());
    threadPool.schedule(() -> {
    
    
        System.out.println("任务被执行,时间:" + new Date());
        try {
    
    
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
    
    
        }
    }, 2, TimeUnit.SECONDS);
}

insert image description here
newWorkStealingPool
Create a thread pool for preemptive execution (task execution order is uncertain). Note that this method can only be used in JDK 1.8+ versions.

public static void workStealingPool() {
    
    
    // 创建线程池
    ExecutorService threadPool = Executors.newWorkStealingPool();
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
        });
    }
    // 确保任务执行完成
    while (!threadPool.isTerminated()) {
    
    
    }
}

insert image description here
ThreadPoolExecutor
The most primitive way to create a thread pool, it contains 7 parameters for setting.

public static void myThreadPoolExecutor() {
    
    
    // 创建线程池
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
    // 执行任务
    for (int i = 0; i < 10; i++) {
    
    
        final int index = i;
        threadPool.execute(() -> {
    
    
            System.out.println(index + " 被执行,线程名:" + Thread.currentThread().getName());
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        });
    }
}

insert image description here

4. Detailed Explanation of ThreadPoolExecutor

After the above study, we also have a certain understanding of the entire thread pool, so how to choose a thread pool?

Let's take a look at the answer given to us by Alibaba's "Java Development Manual":

  • [Mandatory requirements] Thread pools are not allowed to be created using Executors, but through ThreadPoolExecutor. This processing method allows students who write to be more clear about the running rules of the thread pool and avoid the risk of resource exhaustion.
  • Note: The disadvantages of the thread pool objects returned by Executors are as follows:
    1) FixedThreadPool and SingleThreadPool: The allowed request queue length is Integer.MAX_VALUE, which may accumulate a large number of requests, resulting in OOM.
    2) CachedThreadPool: The number of threads allowed to be created is Integer.MAX_VALUE, which may create a large number of threads, resulting in OOM.

So in summary, we recommend using the ThreadPoolExecutor method to create a thread pool, because this creation method is more controllable, and it clarifies the running rules of the thread pool, which can avoid some unknown risks.

Introduction to ThreadPoolExecutor 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, the number of threads that are always alive in the thread pool.

参数 2:maximumPoolSize
The maximum number of threads, the maximum number of threads allowed in the thread pool, and the maximum number of threads that can be created when the task queue of the thread pool is full.

参数 3:keepAliveTime
The maximum number of threads can survive. When there is no task execution in the thread, the maximum thread will destroy part of it, and finally keep the number of core threads.

参数 4:unit:
The unit is used in conjunction with the survival time of parameter 3, which is used together to set the survival time of the thread. The time unit of the parameter keepAliveTime has the following 7 options:

TimeUnit.DAYS:天
TimeUnit.HOURS:小时
TimeUnit.MINUTES:分
TimeUnit.SECONDS:秒
TimeUnit.MILLISECONDS:毫秒
TimeUnit.MICROSECONDS:微妙
TimeUnit.NANOSECONDS:纳秒

参数 5:workQueue
A blocking queue, which is used to store the tasks waiting to be executed by the thread pool, is thread-safe. It includes the following seven types: the more commonly used ones are LinkedBlockingQueue and Synchronous, and the queuing strategy of the thread pool is related to BlockingQueue.

ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。

参数 6:threadFactory
Thread factory, mainly used to create threads, defaults to normal priority, non-daemon threads.

参数 7:handler
Reject policy, the policy when rejecting the task, the system provides 4 options: the default policy is AbortPolicy.

AbortPolicy:拒绝并抛出异常。
CallerRunsPolicy:使用当前调用的线程来执行此任务。
DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务。
DiscardPolicy:忽略并抛弃当前任务。

ThreadPoolExecutor execution process
The execution flow of the key nodes of ThreadPoolExecutor is as follows:

  • Create threads when the number of threads is less than the number of core threads.
  • When the number of threads is greater than or equal to the number of core threads and the task queue is not full, put the task into the task queue.
  • When the number of threads is greater than or equal to the number of core threads and the task queue is full: if the number of threads is less than the maximum number of threads, create a thread; if the number of threads is equal to the maximum number of threads, throw an exception and reject the task.
    insert image description here

ThreadPoolExecutor custom rejection strategy

public static void main(String[] args) {
    
    
    // 任务的具体方法
    Runnable runnable = new Runnable() {
    
    
        @Override
        public void run() {
    
    
            System.out.println("当前任务被执行,执行时间:" + new Date() +
                               " 执行线程:" + Thread.currentThread().getName());
            try {
    
    
                // 等待 1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    };
    // 创建线程,线程的任务队列的长度为 1
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(1, 1,
                                                           100, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1),
                                                           new RejectedExecutionHandler() {
    
    
                                                               @Override
                                                               public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                                                                   // 执行自定义拒绝策略的相关操作
                                                                   System.out.println("我是自定义拒绝策略~");
                                                               }
                                                           });
    // 添加并执行 4 个任务
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
    threadPool.execute(runnable);
}

insert image description here

Guess you like

Origin blog.csdn.net/m0_46638350/article/details/130925464