内容: 1、什么是线程池
2、线程池的优点
3、线程池原理(重点)
4、理解线程池(重点)
5、如何设置线程池
1、什么是线程池
线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
线程的状态:NEW RUNNABLE RUNNING BLOCKED DEAD
2、线程池的优点
(1)、线程是稀缺资源,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以重复使用。
(2)、可以根据系统的承受能力,调整线程池中工作线程的数量,防止因为消耗过多内存导致服务器崩溃。
3、线程池的实现原理
例子: 核心线程数:2
任务队列大小:3
最大线程数:5
(1)首次运行线程池:
同时8个请求: 第1请求和第2请求, 未达到 核心线程数, 直接执行。
第3,4,5个请求,已达到核心线程数, 未达到任务队列大小,直接 进 任务队列。
第6,7,8个请求,任务队列也已经满了,未达到最大线程数(还有 3个线程,也就是5减2等于3),则新开线程。
所以 所有的请求都会成功处理。
假如设置线程的存活时间为1分钟,10分钟后,线程池里面只有2个核心线程。
再次同时8个请求: 第1请求和第2请求 已达到核心线程数,未达到任务队列大小,则进任务队列。
第3请求 ,已达到核心线程数,未达到任务队列大小,则进任务队列。
第4,5,6请求,任务队列已经满了,未达到最大线程数,则新开线程。 (这种情况是CPU没有及时切换到核心线程的情况)。
第7,8请求,只能走拒绝策略了。
所以 不一定所有的请求都会成功处理
但有一种可能会成功处理,那就是入队列的时候,CPU及时切换到核心线程,核心线程立即从队列里面取走。
比如: 第1请求和第2请求 已达到核心线程数,未达到任务队列大小,则进任务队列。
此时CPU切换到核心线程,2个核心线程从队列里面取走了,此时队列为空。
第3,4,5个请求,已达到核心线程数, 未达到任务队列大小,直接 进 任务队列。
第6,7,8个请求,任务队列也已经满了,未达到最大线程数(还有 3个线程,也就是5减2等于3),则新开线程。
4、理解线程池
以java定义的线程池为例:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) ;
corePoolSize:指定了线程池中的线程数量,它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue任务队列中去;
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量;
keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
unit:keepAliveTime的单位
workQueue:任务队列,被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
同步阻塞队列:设置为SynchronousQueue队列,SynchronousQueue是一个特殊的BlockingQueue,每执行一个插入操作就会阻塞
有界的任务队列:有界的任务队列可以使用ArrayBlockingQueue实现
无界的任务队列:有界任务队列可以使用LinkedBlockingQueue实现
优先任务队列:优先任务队列通过PriorityBlockingQueue实现
threadFactory:线程工厂,用于创建线程,一般用默认即可;
自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级
handler:拒绝策略;当任务太多来不及处理时,如何拒绝任务;
AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
5、如何设置线程池
要想合理的配置线程池的大小,首先得分析任务的特性,可以从以下几个角度分析:
- 任务的性质:CPU密集型任务、IO密集型任务、混合型任务。
- 任务的优先级:高、中、低。
- 任务的执行时间:长、中、短。
- 任务的依赖性:是否依赖其他系统资源,如数据库连接等。
(1)根据经验值:性质不同的任务可以交给不同规模的线程池执行。
对于不同性质的任务来说,CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数,IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1,而对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理。
若任务对其他系统资源有依赖,如某个任务依赖数据库的连接返回的结果,这时候等待的时间越长,则CPU空闲的时间越长,那么线程数量应设置得越大,才能更好的利用CPU。
当然具体合理线程池值大小,需要结合系统实际情况,在大量的尝试下比较才能得出,以上只是前人总结的规律。
(2)根据理论值:最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
可以得出一个结论:
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。