线程池是集中管理线程的,以实现线程的重用,降低资源消耗,提高响应速度,提高线程的可管理性等。线程用于执行异步任务,单个的线程既是工作单元也是执行机制,从JDK1.5开始,为了把工作单元与执行机制分离开,Executor框架诞生了,他是一个用于统一创建与运行的接口。Executor框架实现的就是线程池的功能。使用线程池可以进行统一的分配,调优和监控。使用线程池的优势
使用线程池可以进行统一的分配,调优和监控。提交一个任务到线程池中,线程池的处理流程如下:
1、判断核心线程池里的核心线程(corePoolSize)是否已满
2、工作队列是否已经满了
3、判断线程池是否满了【核心线程以外的可以创建的线程,最大线程数】
4、调用executor()方法中创建一个线程,会让这个线程执行当前任务,线程在执行完当前任务以后,队列里有任务就会不停的从BlockingQueue中取任务执行。
(图片来自网络)
线程池的真正实现类是 ThreadPoolExecutor,我们可以实现ThreadPoolExecutor 自定义线程池的方式,其次我们也可以使用Executor框架的 Executors类(java.util.concurrent包)创建封装好的线程。而ThreadPoolExecutor 类参数最多,并且 Executors 封装的线程池底层都是基于ThreadPoolExecutor实现的。
什么是Executors?Executors框架实现的就是线程池的功能。Executors工厂类中提供的newCachedThreadPool、newFixedThreadPool 、newScheduledThreadPool 、newSingleThreadExecutor 等方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
特点:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。定长线程池的大小最好根据系统资源进行设置。
缺点:线程数量是固定的,但是阻塞队列是无界队列。如果有很多请求积压,阻塞队列越来越长,容易导致OOM(超出内存空间)
总结:请求的挤压一定要和分配的线程池大小匹配,定线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
特点:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,他必须保证前一项任务执行完毕才能执行后一项。保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
缺点:缺点的话,很明显,他是单线程的,高并发业务下有点无力
总结:保证所有任务按照指定顺序执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。如果工作线程空闲 60 秒没有被使用,会自动关闭。
特点:newCachedThreadPool创建一个可缓存线程池,如果当前线程池的长度超过了处理的需要时,它可以灵活的回收空闲的线程,当需要增加时, 它可以灵活的添加新的线程,而不会对池的
长度作任何限制
缺点:他虽然可以无线的新建线程,但是容易造成堆外内存溢出,因为它的最大值是在初始化的时候设置为 Integer.MAX_VALUE,一般来说机器都没那么大内存给它不断使用。当然知道可能出问
题的点,就可以去重写一个方法限制一下这个最大值
总结:线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
创建一个定长线程池,支持定时及周期性任务执行。
特点:创建一个固定长度的线程池,而且支持定时的以及周期性的任务执行,类似于Timer(Timer是Java的一个定时器类)
缺点:由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务(比如:一个任务出错,以后的任务都无法继续)。
参数介绍如下:
拒绝策略说明
通过以上两种方式都可以得到一个线程池,日常开发中建议使用第 2 种方式来创建线程池。在阿里巴巴Java开发手册中提到不允许适用Executors创建线程,而是使用ThreadPoolExecutor方式明确线程池运行规则
我们可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池,但是它们的实现原理不同。
线程池拒接收新提交的任务,同时等待线程池里的任务执行完毕后关闭线程池。原理是只是将线程池的状态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。
线程池拒接收新提交的任务,同时立刻关闭线程池,线程池里的任务不再执行。原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止。shutdownNow 会首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。
只要调用了这两个关闭方法的其中一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true。至于我们应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown 来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow。
要合理的分配线程池的大小要根据实际情况来定,简单的来说的话就是根据CPU密集和IO密集来分配
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开
几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。
IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行,
即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
分配CPU和IO密集:
网上能很容易查到以上参考值,真正使用时候还需要考虑以下场景,并在观察一段执行情况后做出优化调整,经验永远大于理论
前一篇:使用Arthas排查性能问题