线程池使用总结
Excutors工厂类
为了更好的控制更多的多线程,JDK提供返回一个固定数量的线程池。
方法:
- newFixedThreadPool()方法:
改方法返回一个固定数量的线程池,改方法的线程数,始终不变,当一个任务提交时,若线程池空闲,则立即执行,若没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行。 - newSingleThreadPool()方法
创建一个单例的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中 - newCachedThreadPool()方法
返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务,则创建线程,若无任务则不创建线程,如果没有任务则线程会在60s后自动回收。 - newScheduledThreadPool()方法:该方法返回一个ScheduledExcutorService对象,但该线程池可以指定线程的数量。
自定义线程池-ThreadPoolExecutors
- 自定义线程池:ThreadPoolExecutor
线程池相关参数的配置
ThreadPoolExecutor(int corePoolSize, //核心线程数,线程初始化就会被创建
int maximumPoolSize, //线程池最大线程数,无界队列时,不起作用
long keepAliveTime, //线程的存活时间
TimeUnit unit, //时间的单位
BlockingQueue<Runnable> workQueue, //阻塞队列,使用无界队列时,拒绝策略无用
ThreadFactory threadFactory,//theadFactory 线程工厂,用于获取一个新的线程,然后把该线程投递到线程池里面去
RejectedExecutionHandler handler) //拒绝策略
1.当设置为有界队列时:
a 若有新的任务需要执行,如果线程实际数小于coreThread,则先创建线程
b.若大于coreSize,则会加入任务队列
c.若队列已满,则在不大于maxinumPoolsize的情况下创建新的线程
d.若线程数不大于maxinumPoolSize会执行拒绝策略
2.当为无界队列时
a.maximumPoolSize不起作用,也没有拒绝策略
b.若有新的任务需要执行,如果线程实际数小于coreThread,则先创建线程
c.若大于coreSize,则会加入任务队列,同时创建新的线程。
如何使用好线程池?
- 线程个数大小的设置
- 线程池相关参数的配置
- 利用Hook嵌入你的行为
- 线程池的关闭
线程池数量的设置
计算机密集型
应用需要非常多的CPU计算资源,避免过多的线程上下文切换
线程数 = CPU核数+1,已可以设置为CPU核数*2,还要看JDK的版本以及CPU配置(服务器的CPU有超线程)
IO密集型
Web应用,涉及到大量的网络传输,不仅如此,与数据库,与缓存间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束后,数据准备好后,线程才会继续执行。对于IO密集型的应用,我们可以多设置线程池中线程的数量,这样就能让等待IO的这段时间内,线程可以去做其它事,提高并发处理效率。线程上下文切换数有代价的
线程数 = CPU核数/(1-阻塞系数) 这个阻塞系数一般为0.8~0.9之间。
套用公司:对于双核CPU来说,比较理想的线程数就是20,当然这不是绝对的,需要根据实际情况以及实际业务来调整:final int poolSIze = (int)(copCore/(1-0.9))
线程池相关参数如何配置?
- 高压线:
- 我们使用线程池的时候都不要选择没有上限限制的配置项
- 第一 我们不要去使用没有上限的线程池和设置无界队列!
- 比如,newCachedThreadPool的设置与无界队列因为某些不可预期的情况,线程池会出现系统异常,导致线程暴增的情况或者任务队列或者任务队列不断膨胀,内存耗尽导致系统崩溃。我们建议自定义线程池来避免改问题,这是在使用线程池的首要选择。小心无大错,千万别过度自信。
- 第二 合理设置线程数量、和线程空闲回收时间,根据具体的任务执行周期和时间去设定,避免频繁的回收和创建,虽然我们使用线程池的目的是为了提升系统性能和吞吐量,但是也要考虑下系统的稳定性,不然出现不可预期问题会很麻!
- 第三,根据实际场景,选择适用于自己的拒绝策略。进行补偿,不要乱用JDK支持的自动补偿机制~尽量采用自定义的拒绝策略去进行兜底!
利用Hook去嵌入你的行为
- 利用Hook,留下线程池执行的执行轨迹
- ThreadPoolExcutor提供了protected类型可以被覆盖的钩子方法,运行用户在执行任务之前或执行之后做一些事情。我们可以通过它来实现比如初始化ThreadLocal、收集统计信息、如记录日志等操作,这类Hook如BeforeExcute和afterExecute。另外还有一个Hook可以用来在任务呗执行完的时候让用户插入逻辑,如reminated。
- Hook方法执行失败,则内部的工作线程的执行将会失败或者中断。
关闭线程池
- 内容当线程池不在被引用并且工作线程数为0的时候,线程池将被终止。我们也可以调用shutdown来手动终止线程池。如果我们忘记调用shutdown,为了让线程资源被释放,我们还可以使用keepAliveTime和allowShutdownHook方法,手工去调用线程池的关闭方法!
- 最稳妥的方式是使用虚拟机Runtime.getRuntime().addShutdownHook方法,手动去调用线程池关闭方法。