线程池的作用想必不用多说,先来看一张java线程池的框架结构图。
重点关注ThreadPoolExecutor类。
ThreadPoolExecutor
该类有四个构造函数,如下:
先来解释下参数的意义,以参数最多的构造函数为例:
corePoolSize和maximumPoolSize
线程池中核心线程数和最大线程数。
当workQueue为无界队列时,maximumPoolSize不起作用(超过核心线程数后任务全部会缓存到队列中,不会创建非核心线程)。
线程池内部处理流程如下:
keepAliveTime
空闲线程的存活时间。
当非核心线程空闲超过keepAliveTime后,该线程会被销毁,默认情况下,keepAliveTime只对超出corePoolSize的线程起作用。
可以通过allowCoreThreadTimeOut(boolean)方法将空闲超时策略应用于核心线程,但是要保证超时时间不为0。
unit
keepAliveTime的单位。
可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS)和毫微秒(NANOSECONDS)。
workQueue
用于缓存任务的阻塞队列。
它是一个BlockingQueue,有如下几种:
ArrayBlockingQueue:
基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)排序元素,在创建队列时,就需要对其指定容量。
LinkedBlockingQueue:
基于链表结构的无界阻塞队列,此队列按FIFO (先进先出) 排序元素,与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况。
SynchronousQueue:
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。
PriorityBlockingQueue:
具有优先级的无限阻塞队列,可根据任务自身的优先级顺序先后执行(总是确保高优先级的任务先执行)。
DelayQueue:
DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
threadFactory
创建线程的工厂,默认使用Executors.defaultThreadFactory()为线程池工厂。
handler
表示当workQueue已满,且池中的线程数达到maximumPoolSize时,线程池拒绝添加新任务时采取的策略,默认为AbortPolicy,即无法处理新任务时抛出异常。也可实现RejectedExecutionHandler接口来自定义拒绝策略。
常用拒绝策略:
1.AbortPolicy:直接抛异常;
2.CallerRunsPolicy:用调用者所在线程来运行任务;
3.DiscardOldestPolicy:丢弃队列最近的任务,并执行当前任务;
4.DiscardPolicy:不处理,丢弃(但不抛出异常)。
Executors
经常有人将Executors,Executor,ExecutorService三者搞混,其实看结构图就可明白,ExecutorService继承了Executor,添加了一些控制线程生命周期的方法,算是扩展了Executor,而Executors根本没有出现在图中,因为Executors本身只是个工具类,用于快捷的创建线程池。
Executors提供了六种创建线程池的方式,分别为fixedThreadPool,cachedThreadPool,singleThreadExecutor,scheduledThreadPool,singleThreadScheduledExecutor(单线程池和计划线程池的组合,将计划线程池的核心线程数置为1),workStealingPool。其中最后一种是基于ForkJoinPool实现的,暂不讨论。
下面分别来看下几种线程池的实现。
fixedThreadPool
核心线程数和最大线程数为同一传入参数,即,所有线程均为核心线程,永不销毁,循环复用,当无空闲线程时,任务进入无界的LinkedBlockingQueue中。
当任务量稳定,没有明显峰值和低谷,并且任务执行的时间不会太短的时候使用。适用于CPU密集型任务。
cachedThreadPool
核心线程数为0,最大线程数无界,超时时间为60s,使用SynchronousQueue队列(不会缓存任务)。即,每来一个任务,如果有空闲线程,则使用,如没有则创建,当线程空闲60s后立即销毁。
适用于处理大量短时间偶发工作任务,即IO密集型任务。
singleThreadExecutor
核心线程数为1,最大线程数也为1,使用LinkedBlockingQueue为缓存队列。即,该线程池只有一个永不销毁的工作线程,如该线程被占用,则进入无界队列中等待。
使用此线程池能保证所有的任务都是被顺序执行,最多会有一个任务处于活动状态。
scheduledThreadPool
核心线程数为传入参数(singleThreadScheduledExecutor将该参数指定为1),最大线程数无界,使用DelayedWorkQueue为阻塞队列。
该线程池适用于定期或者延迟执行的任务。
workStealingPool
Java 8 才加入这个创建方法,其内部会构建ForkJoinPool,利用Work-Stealing算法,并行地处理任务,不保证处理顺序。
总结
如果让你选择,你会选择使用Executors方式创建还是使用ThreadPoolExecutor类去创建呢,我想大多数人会选择前者,因为方便快捷,Java提供的工具类,为什么不用呢?但是阿里规约却明确表示,禁止使用Executors创建线程池,原因如下: