Java 线程池 ThreadPool
看到好的文章怕收藏的多了也不看,看了也忘,想着自己整理一下也能加深自己对线程池的认识,。
问题:
单开线程的方式:new Thread(() -> {…})。已经很简单了,我为什么要用线程池。如果你需要成千上百个呢,这就需要用有一个统一的管理了。
线程池:
它有什么好处呢,减少线程的创建、销毁。反复利用这几个线程来执行所有新老任务。
线程池的源码,下面解读每个参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
1、corePoolSize:核心线程数
线程池在完成初始化之后,线程池中不会有任何线程,线程池会等有任务来的时候再去创建线程。核心线程创建出来后即使超出了线程保持的存活时间配置也不会销毁,就等着新任务进来进行处理。
2、maximumPoolSize:最大线程数
线程池在完成初始化之后,线程池中不会有任何线程,线程池会等有任务来的时候再去创建线程。核心线程创建出来后即使超出了线程保持的存活时间配置也不会销毁,就等着新任务进来进行处理。
3、unit:线程保持的存活时间单位
比如:TimeUnit.MILLISECONDS、TimeUnit.SECONDS。
4、workQueue:任务存储队列
核心线程数满了后还有任务继续提交到线程池的话,就先进workQueue。
workQueue通常情况下有如下选择:
LinkedBlockingQueue:无界队列,意味着无限制,其实是有限制,大小是int的最大值。也可以自定义大小。一般用这个,设置最大值。
ArrayBlockingQueue:有界队列,可以自定义大小,到了阈值就开启新线程(不会超过maximumPoolSize)。
5、threadFactory:用threadFactory来生成新的线程
默认采用的是DefaultThreadFactory,主要负责创建线程。newThread()方法。创建出来的线程都在同一个线程组且优先级也是一样的。
6、handler:拒绝策略
任务量超出线程池的配置限制或执行shutdown还在继续提交任务的话,会执行handler的逻辑。默认采用的是AbortPolicy,遇到上面的情况,线程池将直接采取直接拒绝策略,也就是直接抛出异常。RejectedExecutionException
举例
线程池参数配置:核心线程5个,最大线程数10个,队列长度为100。
那么线程池启动的时候不会创建任何线程,假设请求进来6个,则会创建5个核心线程来处理五个请求,另一个没被处理到的进入到队列。这时候有进来99个请求,线程池发现核心线程满了,队列还在空着99个位置,所以会进入到队列里99个,加上刚才的1个正好100个。这时候再次进来5个请求,线程池会再次开辟五个非核心线程来处理这五个请求。目前的情况是线程池里线程数是10个RUNNING状态的,队列里100个也满了。如果这时候又进来1个请求,则直接走拒绝策略。
JUC包提供的线程池工具类:Executors
1:固定线程数 Fixed
Executors.newFixedThreadPool(n); 存在的问题:没有设置大小的无界队列。
2:单线程的线程池 Single
Executors.newSingleThreadExecutor(n); 存在的问题:core:1 max:1 ,没有设置大小的无界队列。
3:带缓存的线程池 Cached
Executors.newCachedThreadPool(n); 存在的问题:keepAliveTime为60S,意味着线程空闲时间超过60S就会被杀死;这就叫带缓存功能的线程池,max:大小为int的最大值。
4:有调度功能的线程池 Scheduled
Executors.newScheduledThreadPool(n); 存在的问题:max:大小为int的最大值。
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 五秒一次
scheduledExecutorService.schedule(() -> System.out.println(Thread.currentThread().getName()), 5, TimeUnit.SECONDS);
// 首次五秒后执行,其次每隔1s执行一次
scheduledExecutorService.scheduleAtFixedRate(() -> System.out.println(Thread.currentThread().getName()), 5, 1, TimeUnit.SECONDS);
}
所以说线程池还是自己创建最合适啦。
@EnableAsync
@Configuration
@Slf4j
public class AsyncTaskPoolConfig {
@Bean("taskThreadPool")
public ThreadPoolTaskExecutor taskExecutor() {
log.info("start------------------------------->");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(9);
executor.setQueueCapacity(50);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("echoExecutor-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//加载
executor.initialize();
return executor;
}
}
线程数多少合适?
CPU密集型(比如加密、各种复杂计算等):建议设置为CPU核数+1。
*耗时IO操作(比如读写数据库,压缩解压缩大文件等等):一般会设置CPU核数的2倍。当然也有个很牛X的计算公式:线程数=CPU核数 (1+平均等待时间/平均工作时间)
我用的是 core:CPU+1;max:CPU*2+1