爱陪小樱桃

导航

 

线程池的概念:

首先从理论到实践:

为什么要用线程池-到怎么用线程池:

1.为什么用?
“池”的概念:频繁创建、销毁线程开销,这种大道你无法想象。
线程是操作系统的资源,创建线程需要分配内存,初始化栈的空间,销毁线程需要回收资源,若果每次处理一个任务:new Thread(),任务执行完就销毁,在高并发下,线程创建和销毁的开销基本会超过任务执行本身的耗时。

这就是为啥创建“池”的原因:提前创建一批线程,任务来了直接用,任务执行完不销毁,继续等待下一个任务,这个数据库连接池“HTTP”逻辑是完全一致的,通过复用资源,降低频繁创建销毁的开销 。

为啥尽量不用:Executor来创建线程池:

ExecutorService pool= Executor.newFixedThreadPool(10);
为啥尽量不用:有的公司是禁止的,因为Executor 隐藏有巨大的风险:
这个里面的方法分析仪一下

newFixedThreadPool()这个方法:它创建的线程池线程数和最大线程数据是相等的,使用的队列是:LinkBlockingQueue,而这个队列默认的容量是:(约是21亿),如果任务的提交速度大于处理速度,直接导致内存溢出OOM;

2.newCachedThreadPool()这个方法:核心线程数据量是0,最大线程数Integer.MAX_VALUE,如果瞬间引入大量的任务,他会无限制的创建线程,最终导致操作系统资源耗尽,系统崩溃。
3.newSigleThreadExecutor(),本质是单线程池的,队列同样是linkedBlockingQueue(容量同样是21亿),一样会引发OOM;

所以说:生产上要手动创建线程ThreadPoolExecutor,自己控制容量,最大线程数量,才能保证 系统的稳定性

核心线程数:

1.corePoolSize:线程池长期维持的线程数量,即使这些线程处于空闲状态,也不会被销毁,(除非设置了allowCoreThreadTimeOut).

2.maximumPolloSize(最大线程数):
线程池允许创建最大的线程数,当核心线程和队列都满时,会创建非核心线程,知道达到这个数量。

3.keepAliveTime&unit(空闲线程存活时间):非核心线程空闲超过这个时间,就会被销毁,核心线程默认不会被销毁,除非设置了(allowCoreThreadTimeOut(true))

4.workQueue(阻塞队列):
用于存储待执行的任务,当核心都繁忙的时候,任务会被放在队列里面等待。

5.threadFactory(线程工厂):用于创建线程,可自定义线程名称,优先级,是否为守护线程等等。
6.handler(拒绝策略):
当线程数达到最大,队列也满时,新提交的任务会触发拒绝策略。

线程池的执行顺序:

正确的顺序是:核心---队列---非核心---拒绝策略。

具体流程:
1.先去核心是否满,未满直接创建核心线程执行任务。
2.如果核心线程已满,任务会被放入队列等待。
3.如果队列满了,会创建非核心线程池。
4.如果线程已经达到最大,队列也满了,就会触发拒绝策略。

首先:队列是缓冲任务的,能避免不必要的线程的创建,只有队列满了,才说明任务量确实超过了核心线程的处理能力,这时候在创建非核心线程来分担压力。

任务太多了如何优雅的降级:

1.明确告诉:默认的是明确的告诉异常,RejectedExecutionException,调用方可以捕获异常进行重试和补偿。适用于任务不能丢的场景。调用方可以进行补偿。

2.callerRunsPolicy:提交任务的线程,自己执行该任务,比如线程提交的任务就有线程执行,这会减缓任务提交的速度,起到“削峰”的作用。

3.DiscardPolicy:默默丢弃任务:不抛异常,不做任何处理,适用:“可以丢的场景”,比如日志收集。

4.DiscardOldestPolicy:丢弃队列中最老的任务:然后将新任务加入队列,适用于:新任务比旧任务更重要的场景。

动态调整线程:

如果线上运行一段时间,发现线程池参数不合理(比如核心数太小,队列容量太小),不需要重启应用,就可以动态调整,可以根据监控数据自动调优。

当然具体的参数的设置,可以通过压测来进行验证,大家可以通过jemeter来讲进行压测试试。

如何设置呢?

线程池的设置是调优的关键,盲目设置10、20,往往会导致性能瓶颈,我们可以根据任务类型来选择策略:

CPU密集型:

(如果加密,计算,压缩)
这类任务主要是消耗的是CPU,线程数据设置:CPU的核数+1,"+1"是为了防止某线程因内存页错误等原因暂停时候,有备用的线程CPU不空闲。

IO密集型任务:

(比如读文件,网络请求,数据库查询)
这类任务大部分时间在等待IO,CPU空闲率高,线程数可以设置为CPU2设置更多。
对于:混合型任务(既有CPU的计算,又有IO等待)有一个经验公式:
线程数=CPU核数
(1+IO耗时/cpu耗时间)

例如CPU的核心数是8,任务CPU的耗时100ms,IO是400ms,则线程数=8*(1+400/100)=40;
这个只是理论要通过压测来调整,找到最优秀的线程。

扩展了解:

1.核心线程的“懒加载”:线程池:默认是:“懒加载”核心线程的,既只有任务提交的时候才会创建核心线程,这会导致第一批任务的时间较长(为什么?因为要先创建线程),解决方法:是prestartAllCoreThread()方法,在创建线程池的时候预热所有的核心线程。

2.任务异常处理:
1.在使用execute()提交任务的时候,异常会打到控制台上,但是不会影响其他的任务,抛出异常后的线程会被销毁,线程池会创建新的线程补充。

2.使用submit()提交任务的时候,异常会被封装到Future()中,必须调用Future.get()才能感知到异常,否则会被吞掉。

最佳方案:在任务加载的时候,主动添加try。。。catch,捕获异常并记录日志,不要依赖线程池的默认处理。

优雅关闭线程池:

shutdown()
1.温柔的关闭,不在接受新的任务,会执行完队列中级正在执行的任务后关闭。
2.暴力关闭()
shutdownNow():立即中断执行任务,清空队列,返回执行的任务列表。

优雅关闭线程池:先调用:shutdown()在调用awaitTermination()等待任务执行完毕,如果超过仍未执行完毕再调用shutdown()强制关闭。

posted on 2025-11-17 22:56  cherry小樱桃  阅读(23)  评论(0)    收藏  举报