1、什么是线程池?
线程池就是创建若干个可执行的线程放入一个池(容器)中,有任务需要处理时,会提交到线程池中的任务队列,处理完之后线程并不会被销毁,而是仍然在线程池中等待下一个任务。
2、为什么要用线程池?
频繁创建线程,然后重复的从就绪到运行状态,效率非常低。
采用线程池直接实现复用,不用频繁创建,也不用等待CPU的调度,效率非常高。
3、线程池的作用
第一:降低资源消耗。通过复用已创建的线程,可以降低线程创建和销毁造成的资源消耗。
第二:提高响应速度(提高效率)。线程池中的线程不会阻塞、死亡,一直处于运行状态,当任务到达时,任务可以不需要等待线程的创建和CPU调度就可以立即执行任务。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
第四:提供更强大的功能:线程具备可扩展性,允许开发人员向其中增加更多的的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行缺点:比较消耗CPU
4 、线程池的创建方式(官方推荐自定义)
阿里巴巴开发手册中,强制要求线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式可以规避资源耗尽的风险。因为
因为Executors已封装好的几个线程池,底层都是基于无界队列实现,缓存的线程队列可以无限的存放容量大小,有可能耗尽内存资源,导致内存溢出。
5、为什么阿里官方推荐基于ThreadPoolExecutor自定义线程池?
1)因为已封装好的几个线程池,底层都是基于无界队列实现,缓存的线程队列可以无限的存放容量大小,有可能会内存溢出。
2)无界队列,会造成线程池中的最大线程池数无效。
6、构造函数参数解析
corePoolSize: 核心池的大小。 当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
maximumPoolSize: 线程池最大线程数,它表示在线程池中最多能创建多少个线程;队列满的时候会创建新的线程。
keepAliveTime: 表示线程没有任务执行时最多保持多久时间会终止。因为线程一直处于运行状态非常消耗CPU。
unit: 参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:
workQueue:工作队列,先进先出。队列底层,有基于数组实现,也有基于链表实现,如果查询频繁,使用数组;如果修改频繁,使用链表。
阻塞队列:从队列中取出元素的时候,如果队列中没有元素,会进行指定时间的等待,如果任然没有新的元素进来,则返回null。
往队列中添加元素的时候,如果队列已满,会进行指定时间的等待,如果任然没有元素被取出,则返回false。
handler: 自定义封装拒绝策略回调方法。
7、线程池底层原理分析
执行任务的线程数 < 核心线程数时,新任务直接复用线程池中的线程;
执行任务的线程数 ≥ 核心线程数时,新任务存放在缓存队列中;
执行任务的线程数 > 核心线程数,且缓存队列满时,创建新线程执行任务;
执行任务的线程数 > 最大线程数,且缓存队列满时,对任务采取进拒绝策略。1)提交任务的时候比较核心线程数,如果当前执行任务线程数量小于核心线程数,则直接复用线程执行。
2)如果执行任务的线程数量等于核心线程数,则缓存到队列中。
3)如果缓存队列满了,且执行任务的线程数小于最大线程数,则创建线程执行。
4)如果队列已满且执行任务的线程数等于最大线程数,则走拒绝策略。
注意:最大线程数=核心线程数+非核心线程数。如果非核心线程数在一定时间没有执行任务,为了避免浪费内存,线程会被销毁。
8、缓存队列满了,如何拒绝任务?
如果队列满了,且执行任务的线程数>最大线程数,则当前线程走拒绝策略。可以自定义异拒绝异常。线程池会调用rejectedExecutionHandler来处理这个任务。
1)AbortPolicy 丢弃任务,抛运行时异常(默认)。如果没有设置,默认是AbortPolicy,会抛出异常。可以采用异常捕获,将拒绝的线程任务记录到日志表中,后期做定时任务补偿。
2)CallerRunsPolicy 执行任务。用main方法执行被拒绝的线程。
3)DiscardPolicy 忽视,什么都不会发生
4)DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
5)实现RejectedExecutionHandler接口,可自定义处理器.
9、合理配置线程池
1)CPU密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那些。2)IO密集
IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
3)配置
① CPU密集型任务应配置尽可能小的线程,如配置CPU个数+1的线程数;
② IO密集型任务应配置尽可能多的线程,因为IO操作不占用CPU,不要让CPU闲下来,应加大线程数量,如配置两倍CPU个数+1;
③ 对于混合型的任务,如果可以拆分,拆分成IO密集型和CPU密集型分别处理,前提是两者运行的时间是差不多的,如果处理时间相差很大,则没必要拆分了。4)最佳线程数
最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目
比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目5)总结
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。 最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目
以上公式与之前的CPU和IO密集型任务设置线程数基本吻合。
CPU密集型时,任务可以少配置线程数,大概和机器的cpu核数相当,这样可以使得每个线程都在执行任务
IO密集型时,大部分线程都阻塞,故需要多配置线程数,2*cpu核数