Table of Contents
一、线程池的优势?
- 重用存在的线程。减少线程创建、消亡的开销,提高性能
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
二、四个构造方法
ThreadPoolExecutor类是创建线程池的核心类
介绍四种构造方法
三:线程池参数解释
corePoolSize | 核心线程数大小,队列没满时,只会创建和使用核心线程数大小 |
maximumPoolSize | 非核心线程数(maximumPoolSize-corePoolSize),也叫最大线程数,队列满了之后还有需要入队的产品,就会创建非核心线程来使用,产品出队列给非核心线程工作,需要入队的产品继续入队 |
keepAliveTime | 超时时间大小设置。非核心线程超过这个时间,就会被回收,被视为没有用线程(核心线程默认不会被回收) |
unit | 设置超时时间单位。有 TimeUnit.DAYS; //天 TimeUnit.HOURS; //小时 TimeUnit.MINUTES; //分钟 TimeUnit.SECONDS; //秒 TimeUnit.MILLISECONDS; //毫秒,常用 TimeUnit.MICROSECONDS; //微妙 TimeUnit.NANOSECONDS; //纳秒 |
workQueue | 线程阻塞队列选择,常用有ArrayBlockingQueue、LinkedBlockingQueue |
threadFactroy | 创建线程的方式,工厂。可以用默认的,也可以apache和guava等。下面介绍 |
handler | 拒绝策略,当阻塞队列满时,工作线程又已经达到最大数,此时选择采用拒绝策略来应对这种情况,默认是抛出异常这种拒绝策略。 |
allowCoreThreadTimeOut | 控制是否允许核心线程超时退出。设置为true运行核心线程数超时被回收,默认为false |
poolSize | 线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize |
例子说话:
当corePoolSize:5 maximumPoolSize:6 workQueue:5的时候,分析
threadFactroy是一个老板。有一个小仓库(阻塞队列),可以存放5个产品。
场景1:产品到了3个,不往仓库丢,老板直接召了3名职工(corePoolSize)负责完成产品包装。
场景2:产品到了5个,不往仓库丢,老板直接召了5名职工(corePoolSize)负责完成产品包装。
场景3:产品到了6个,往仓库丢一个,老板召了5名职工(corePoolSize)负责完成产品包装。剩下一个等待第一个职工完成后由它来负责
场景4:产品到了11个,往仓库丢5个,老板召了5名职工(corePoolSize)负责完成产品包装。发现还有一个产品没有放下,仓库满了,此时老板用最后的资金又召了一名职工(maximumPoolSize=5+1=6),此时从仓库里拿出一个产品给这个职工包装,还有一个产品放入仓库。
场景5:产品到了12个,往仓库丢5个,老板召了6名职工(maximumPoolSize=6),发现还有一个产品没有放下仓库,仓库已满,职工又全部在工作中,老板也没有资金召职工,此时采用拒绝策略来处理。
场景6:产品到了11个,往仓库丢5个,老板召了5名职工(corePoolSize)负责完成产品包装。发现还有一个产品没有放下,仓库满了,此时老板用最后的资金又召了一名职工(maximumPoolSize=5+1=6),此时从仓库里拿出一个产品给这个职工包装,还有一个产品放入仓库。当时间已经过了一个月(keepAliveTime)时,产品数都是在1-10个之间,最后召了那名职工没事做,此时老板将它开掉,节约薪资。
四、参数合理配置
(1)CPU密集型
CPU密集型的意思就是该任务需要大量运算,而没有阻塞,CPU一直全速运行。
CPU密集型任务只有在真正的多核CPU上才可能得到加速(通过多线程)。
CPU密集型任务配置尽可能少的线程数。
CPU密集型线程数配置公式:CPU核数+1个线程的线程池
(2)IO密集型
IO密集型,即该任务需要大量的IO,即大量的阻塞。大量的文件操作。
在单线程上运行IO密集型任务会导致浪费大量的CPU运算能力浪费在等待。
所以IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要利用了被浪费掉的阻塞时间。
第一种配置方式:
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程。
配置公式:CPU核数 * 2 + 1。
第二种配置方式:
IO密集型时,大部分线程都阻塞,故需要多配置线程数。
配置公式:CPU核数 / 1 – 阻塞系数(0.8~0.9之间)
比如:8核 / (1 – 0.9) = 80个线程数
五、类层级关系
六、创建线程池一个池并分析实例?
ExecutorService executorService = new ThreadPoolExecutor(5, 6, 10L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory());
for (int i = 0; i < 11; i++) {
executorService1.execute(new Thread() {
@Override
public void run() {
System.out.println("aaa");
try {
sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
Thread.sleep(200);
System.out.println( ((ThreadPoolExecutor) executorService).getActiveCount());
executorService.shutdown();
代码解析:创建5个corePool核心线程、1个非核心线程(maximumPoolSize=1+5=6)、5个ArrayBlockingQueue<>有界队列,非核心线程过时策略是10毫秒被回收,采用默认工厂场景线程池。
当有11个任务到来时(这里11个线程理解成11个产品或任务),执行情况分析如下:
运行结果(输出6个活跃线程,说明非核心线程也参与了工作)
当有12个产品/线程到来时,启动默认拒绝策略,抛出异常
当有10个产品时,不会使用非核心线程数,因为队列刚好放的下核心线程未处理剩下的产品
七、线程池的正确选择
由Executors类来创建线程池默认不推荐,可能会造成oom异常,创建方式很多,如下
但是这些场景方式,默认的阻塞队列长度是Integer.MAX_VALUE,就是2^32-1
阿里巴巴都不推荐使用!!!
创建线程池的正确方式:
避免使用Executors创建线程池,主要是避免使用其中的默认实现,那么我们可以自己直接调用ThreadPoolExecutor
的构造函数来自己创建线程池。在创建的同时,给BlockQueue
指定容量就可以了,也可以通过设置自己的工厂来创建线程。
创建线程的工厂都由自己指定,使用apache和guava等,这里没有他们的maven依赖,搞不定。。
八、Java中的几种阻塞队列
一般使用ArrayBlockingQueue和LinkedBlockingQueue设置队列大小
参考博客:https://blog.csdn.net/gaotiedun1/article/details/86606135