为什么使用线程池
- 降低资源消耗:可以重复利用已经创建的线程,减少创建、销毁线程的开销。
- 提高响应速度:任务到达的时候可以直接利用线程池种存在的线程,不需要再创建线程来响应任务。
- 提高线程的可管理性:线程池可以对线程进行统一的分配、监控和调优。
线程池工作流程
线程池核心参数
corePoolSize:核心线程数,线程任务提交时,线程池中线程数量小于corePoolSize时创建线程执行任务,核心线程不会被销毁。销毁核心线程可以设置allowCoreThreadTimeOut(true),核心线程在超过keepAliveTime之后会销毁。
workQueue:等待队列,核心线程数满了之后,将任务加入等待队列等待线程执行,等待队列中只会加入经由execute方法加入的线程。
maximumPoolSize:线程池最大数量,当等待队列满了,线程池中数量小于maximumPoolSize时创建线程执行任务
keepAliveTime:当线程池中的数量大于corePoolSize,空闲的线程空闲时间超过keepAliveTime将会销毁。
handler:当等待队列已满,线程池中的线程池数量达到了maximumPoolSize,此时会调用线程池的拒绝策略拒绝提交的任务,默认拒绝策略为AbortPolicy
threadFactory:用于线程池中线程的创建。
TimeUnit:keepAliveTime的单位
任务队列的类型
ArrayBlockingQueue:基于数组的有界队列,按照FIFO的顺序对元素进行排序
LinkedBlockingQueue:基于链表的无界阻塞队列,按照FIFO的顺序对元素进行排序。默认和最大长度为Integer.MAX_VALUE。Executors.newFixedThreadPool、Executors.newSingleThreadExecutor应用的是该阻塞队列。
PriorityBlockingQueue:支持优先级的无界阻塞队列,默认情况下采用自然顺序生序排列
SynchronizedQueue:不存储元素的阻塞队列,每个插入操作必须要等到另一个线程调用移除操锁否则插入操作一直处于阻塞状态。Executors.newCachedThreadPool使用了该队列
线程池的拒绝策略
AbortPolicy:直接抛出异常
CallerRunsPolicy:只用调用者所在线程来运行任务
DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务,一直重复此过程
DiscardPolicy:不处理任务,直接丢弃
线程池提交任务的方式
execute():用于提交不需要返回值得任务,无法判断任务是否被成功执行
submit():用于提交需要返回值的任务,线程池会返回一个Future的对象,通过future.get()方法来获取返回值,该方法会阻塞当前线程直到任务完成。
关闭线程池
shutdown():将线程池状态设置为SHUTDOWN,不会立即关闭线程池,此时线程池不再接受新任务,等待缓存队列中的任务全部执行完成后终止线程
shutdownNow():将线程池状态设置为STOP,立即终止线程池,尝试中断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
只要调用了关闭方法中的任意一个,调用isShutDown方法返回true,当所有任务都关闭后才表示线程池已经关闭成功,调用isTerminate方法返回true。
合理配置线程池
为了合理的配置线程池,需要考虑线程池需要分心任务的特征,不同任务类型创建不同的线程池
- 任务类型:CPU密集型任务、IO密集型任务或者混合型任务
- 任务的优先级:高、中、低
- 任务执行的时间:长、中、短
- 任务的依赖性:是否依赖于其它的资源,例如数据库连接
CPU密集型任务:线程池的数量应该配置的少,N(CPU) +1
IO密集型任务:线程不是一直在执行任务,应该创建尽可能多的线程, 2*N(CPU)
混合型任务:如果任务可以拆分,将任务拆分成CPU密集和IO密集任务分别创建线程池。
可以通过Runtime.getRuntime().availableProcessors()来获取当前设备cpu个数。
优先级不同的任务可以采用PriorityBlockingQueue来处理
线程池监控
可以通过监控线程池的相关属性,方便快速定位线程池的问题
taskCount:线程池需要执行的任务大小
completedTaskCount:线程池运行过程中已完成的任务数量 <=taskCount
largestPoolSize:线程池曾经创建的最大线程数量
getActiveCount:获取存活的线程数
getPoolSize:线程池的线程数量
线程池的实现
ThreadPoolExecutor用来执行被提交的任务,常用的线程池
FixedThreadPool:
创建固定长度的线程池,常用于需要限制当前线程池数量的应用场景,构造方法如下
核心线程数和最大线程数都为创建线程池时设定的常量,阻塞队列为LinkedBlockingQueue,不会调用线程池的拒绝策略
SingleThreadExecutor:
线程池中只有一个线程,适合需要保证执行顺序并且在任意时刻不会有多个线程同时运行的场景,构造方法如下
核心线程数和最大线程数均设置为1,如果设置FixedThreadPool的线程池数量为1,也能实现一个SingleThreadExecutor
CachedThreadPool:
是一个大小无界的线程池,能够根据需要创建线程池
线程池中不设置核心线程数,超过60s的线程会被线程池销毁,下次任务提交的时候再重新创建新的线程。如果线程池的处理任务的速度低于主线程提交任务的速度,由于SynchronousQueue不会存储线程,因此线程池将会不断的创建新线程来处理任务,可能会耗尽CPU资源。
ScheduledThreadPoolExecutor:
适用于需要多个后台线程执行周期任务,同时需要限制后台运行线程的数量,使用延迟队列作为等待队列
SingleThreadScheduledExecutor:
适用于需要单个线程执行周期性的任务,并且需要保证任务的执行顺序。
有两个重要的方法
initialDelay:初始延迟时间
period:在period后再次执行任务,如果任务的执行时间>period, 会等待当前任务执行完成之后再进行后续任务的执行
initialDelay:初始延迟时间
delay:当前任务完成之后延迟delay时间之后再执行第二次任务