为什么要用线程池
线程高并发时期的创建与销毁都需要消耗大量的性能,线程的创建、销毁与数量无法管控
使用线程池可以有效的管理线程,提高运行速度与效率
常见的几种线程池及创建方式
Executors.newFixedThreadPool(poolSize); //固定大小线程池
Executors.newCachedThreadPool(); //可缓存线程池
Executors.newSingleThreadExecutor(); //单线程池
Executors.newScheduledThreadPool(poolSize); //定时线程池
上面几种常用的线程创建方式底层都是通过这个方法去实现的
线程池的原理分析
1.线程池的调用是通过execute方法
2.判断是否超过核心线程数,如果没有则创建线程(为什么要创建?线程池构建是没有创建?)
3.调用线程执行任务(都说线程池可以重复利用线程,那么它是怎么去实现的?)
4.不断的从阻塞队列中获取任务(如果没有任务了怎么办?线程需要一直运行嘛?)
5.如果核心线程数满了,则添加到队列中
6.如果阻塞队列已经满了就判断线程池中工作线程数是否超过最大线程数
7.如果超过了就执行拒绝策略
8.如果没超过就创建线程,并执行
线程池运行过程中的疑问
-
线程是在什么时候创建的?
默认情况下,创建线程池不会创建线程,会等待运行时,逐个创建线程
也可以通过:
prestartCoreThread():初始化一个核心线程;
prestartAllCoreThreads():初始化所有核心线程 -
线程池是如何实现重复利用线程的?
创建线程后创建worker,通过worker.start方法最终调用runworker方法
我们不难发现他是通过while方法来实现循环利用的
while条件是:
1.task!=null,传过来的任务不是空的
2.getTask()!=null,从队列中获取到的任务不是空的,代表队列中有任务 那么传过来的任务肯定有运行完的时候,难道队列的任务能一直都有?
3.如果队列中没有任务了,线程还怎么一直运行?怎么重复利用?
关键在于getTask方法
1.getTask通过自旋不断的尝试获取任务
2.正常是核心线程数以内的话,workQueue.take方法在拿不到线程时会阻塞
3.当设置核心线程也销毁或者当前运行线程数超过核心线程数时,通过workQueue.poll(超时时间,超时单位),超过设置时间后会返回一个null,runWorker跳出循环,结束线程
4.既然阻塞了,那什么时候唤醒,当队列添加任务时会唤醒
线程池参数的一些思考
-
核心线程数设置多少比较合适
根据cpu的核心数和线程的类型(io密集型,cpu密集型),经验设置为cpu核心数+1
-
为什么要设置最大线程数
1.高并发情况下,现有线程数量处理不了任务,会导致任务积压,阻塞队列占用内存,任务处理速度跟不上业务处理
2.直接设置过多的核心线程,会导致资源的浪费 -
超时时间与单位
为了销毁超出核心数的线程,使得资源与效率达到平衡
阻塞队列需要用到这个参数来控制 -
阻塞队列
光靠几个设置的线程如何能同时完成成百上千的任务,完成不了也不能一丢了之,所以存放在阻塞队列中
线程池为何能重复利用,就是因为可以不断的执行任务,这就需要从某处获取 -
线程工厂
自定义创建线程的方式 -
拒绝策略
常用的策略有:抛出错误,直接丢弃,使用主线程运行,执行执行任务的run方法
阻塞队列是有自己的大小的,所以拒绝策略是为了阻塞队列而存在的
从某种意义上说可以更细粒度的控制线程池