线程池ThreadPoolExecutor使用指南
🧐使用线程池的好处是什么?
统一管理,减少资源获取创建的开销,提高利用率。
🔧线程池的参数
ThreadPoolExecutor
3 个最重要的参数:
-
corePoolSize
: 任务队列未达到队列容量时,最大可以同时运行的线程数量。 -
maximumPoolSize
: 任务队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数。 -
workQueue
: 新任务来的时候会先判断当前运行的线程数量是否达到核心线程数,如果达到的话,新任务就会被存放在队列中
ThreadPoolExecutor
其他常见参数 :
-
keepAliveTime
:线程池中的线程数量大于corePoolSize
的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了keepAliveTime
才会被回收销毁。 -
unit
:keepAliveTime
参数的时间单位。 -
threadFactory
:executor 创建新线程的时候会用到。 -
handler
:拒绝策略
🤔你是如何创建线程池的?为什么不建议直接用Executors
创建线程池?
一般不建议直接用Executors
创建线程池的,这种方式缓冲队列没有限制合适的大小(默认整形的最大值),处理不好会有OOM的风险。而是通过 ThreadPoolExecutor
构造函数的方式,这样的好处就是更加明确线程池的运行规则,规避资源耗尽的风险。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
✨线程池的策略有哪些?
当前同时运行的线程数量达到最大线程数量并且队列也已经被放满了任务时:
常见的策略有拒绝策略:
-
AbortPolicy
抛出RejectedExecutionException
来拒绝新任务的处理。 -
CallerRunsPolicy
:调用执行自己的线程运行任务,由主线程自己来执行这个任务。(不允许丢弃任务适合该策略)
不常见策略(了解既可):
-
ThreadPoolExecutor.DiscardPolicy
:不处理新任务,直接丢弃掉。 -
ThreadPoolExecutor.DiscardOldestPolicy
:此策略将丢弃最早的未处理的任务请求。
📌Springboot下使用示例
当您使用ThreadPoolTaskExecutor
配置线程池时,可以参考以下示例代码:
ThreadPoolConfig
类通过@Configuration
注解标记为配置类,并定义了一个名为taskExecutor
的Bean,类型为ThreadPoolTaskExecutor
。在taskExecutor()
方法中,设置了线程池的核心线程数为10,最大线程数为20,队列容量为30,线程名前缀为"MyThread-",然后调用initialize()
方法初始化线程池,并返回配置好的ThreadPoolTaskExecutor
实例
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 设置核心线程数
executor.setMaxPoolSize(20); // 设置最大线程数
executor.setQueueCapacity(30); // 设置队列容量
executor.setThreadNamePrefix("MyThread-"); // 设置线程名前缀
executor.initialize(); // 初始化线程池
return executor;
}
}
异步任务的地方注入TaskExecutor Bean,并调用其execute()方法来提交任务:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
@Service
public class MyService {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
public void executeAsyncTask() {
taskExecutor.execute(() -> {
// 执行异步任务的逻辑
System.out.println("Async task executed by thread: " + Thread.currentThread().getName());
});
}
}
然而,也有一些特殊场景下可能需要手动创建线程池实例:
- 定制化需求:如果需要对线程池进行高度定制,例如设置特定的线程池参数或使用特殊的线程工厂等,可能需要手动创建线程池实例。
- 独立使用:某些情况下,线程池只在一个特定的类或模块中使用,并且不需要在整个应用中共享,这时候手动创建线程池可能更合适。
大多数情况下建议将线程池实例交给Spring容器管理,以便统一管理和便于集成;如果手动创建了线程池实例,建议在某个特定的时机手动调用线程池的shutdown()
方法来优雅地关闭线程池,释放资源。
但这个特定时机怎么定义?使用CountDownLatch
或许可以符合你的场景。
⛽CountDownLatch
的使用
CountDownLatch
的作用就是 允许 多个线程阻塞在一个地方,直到所有线程的任务都执行完毕。那么手动创建的线程就可以通过shutdown()
方法来优雅地关闭线程池了。
package com.fjh.demo.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @ClassName: CustomThreadPoolExample
* @Description: TODO 手动自定义创建线程池,CountDownLatch使用场景
* @Author: fengjiahao
* @Date: 2024/6/15 21:26
*/
public class CustomThreadPoolExample {
public static void main(String[] args) throws InterruptedException {
int numThreads = 5;
// 核心线程数
int corePoolSize = 3;
// 最大线程数
int maxPoolSize = 5;
// 线程空闲时间
long keepAliveTime = 10;
// 任务队列,使用有界队列ArrayBlockingQueue
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize, // 核心线程数
maxPoolSize, // 最大线程数
keepAliveTime, // 线程空闲时间
TimeUnit.SECONDS, // 时间单位
new ArrayBlockingQueue<>(3) // 任务队列
);
CountDownLatch latch = new CountDownLatch(numThreads);
for (int i = 0; i < numThreads; i++) {
int finalI = i;
executor.execute(() -> {
try {
System.out.println("Thread【"+ finalI +"】 started execute: " + Thread.currentThread().getName());
Thread.sleep(2000); // 模拟线程执行任务
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
latch.countDown(); // 每个线程执行完成后调用countDown方法
}
System.out.println("Thread finished: " + Thread.currentThread().getName());
});
}
latch.await(); // 等待所有线程执行完成
System.out.println("All threads have finished. Continuing main thread.");
executor.shutdown(); // 关闭线程池
}
}
☕最后
其实java8中有更加优雅的方式处理这种场景CompletableFuture
,有兴趣的同学可以去了解下🙂