前言
线程池的基本思想是一种对象池的思想,开辟一块内存空间,里面存放了众多未死亡的线程,池中线程执行调度由池管理器来处理。当有线程任务时(线程任务保存在队列里),从池中取一个线程,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。
JavaExecutors类中提供了6种线程池:
- newFixedThreadPool
 - newCachedThreadPool
 - newSingleThreadExecutor
 - newScheduledThreadPool
 - newSingleThreadScheduledExecutor
 - newWorkStealingPool
 
其实这6种线程池都是对线程池类ThreadPoolExecutor的封装,以Executors.newFixedThreadPool()方法为例:
1  | public static ExecutorService newFixedThreadPool(int nThreads) {  | 
下面先介绍线程池类ThreadPoolExecutor,接着按顺序介绍每种线程池的特点,其中第6个很少使用,所以忽略不介绍。
线程池类ThreadPoolExecutor
ThreadPoolExecutor 有多个重载构造函数,下面是它参数最全的一个:
1  | public ThreadPoolExecutor(int corePoolSize,  | 
下面介绍每个参数的含义。
核心线程数corePoolSize
指核心线程的数量。
“核心线程”、“非核心线程”是一个虚拟的概念,并没有哪些线程被标记为“核心”或“非核心”,这个“核心线程数量”仅仅指线程池中会永远保持几个线程活着不被销毁,即使线程池并没有任务要做。
最大线程数maximumPoolSize
指线程池允许创建的最大线程数。
如果任务队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。所以只有任务队列满了的时候,这个参数才有意义。因此当你使用了无界任务队列的时候,这个参数就没有效果了。
keepAliveTime和TimeUnit
指线程活动保持时间,即当线程池的线程空闲后,保持存活的时间。
所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率,不然线程刚执行完一个任务,还没来得及处理下一个任务,线程就被终止,而需要线程的时候又再次创建,刚创建完不久执行任务后,没多少时间又终止,会导致资源浪费。
注意:这里指的是核心线程池以外的线程。可以设置 allowCoreThreadTimeout = true ,这样就会让核心线程池中的线程也有存活时间的控制。
任务队列workQueue
用来保存“等待被执行的”任务的阻塞队列。即当线程池中无可用线程时,新的任务会被放到此队列中等待被执行。
阻塞队列的含义是,当一个线程从队列中取元素时,队列为空,会要求该线程等待直到队列中有元素可取;当一个线程往队列中添加元素时,队列已满,会要求该线程等待直到队列有位置。所以叫做“阻塞”。非阻塞队列就不会要求线程等待,直接返回操作成功或失败。在 Java 中,阻塞队列就是实现了
BlockingQueue接口的类,非阻塞队列就没实现该接口,例如 LinkedList 类。
一般来说可以选择如下阻塞队列:
- ArrayBlockingQueue:基于数组的有界阻塞队列
 - LinkedBlockingQueue:基于链表的阻塞队列
 - SynchronousQueue:一个不存储元素的阻塞队列
 - PriorityBlockingQueue:一个具有优先级的阻塞队列
 
注意:设置阻塞队列时应该指定队列长度,特别是LinkedBlockingQueue,它默认的长度是Integer.MAX_VALUE,会导致队列太长,进而导致OOM。
Integer.MAX_VALUE = 2^31 - 1 = 2147483647 。 Integer.MIN_VALUE = -2^31 = -2147483648
threadFactory
指创建线程的工厂:可以通过线程工厂给每个创建出来的线程设置更加有意义的名字。
默认:DefaultThreadFactory
线程饱和策略RejectedExecutionHandler
指拒绝执行任务的处理,可以理解为饱和策略。当任务队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。
默认:AbortPolicy
- AbortPolicy:直接抛出异常 RejectedExecutionException
 - CallerRunsPolicy: 由调用者所在的线程来运行任务。一般是由 main 线程来执行,会导致 main 线程阻塞
 - DiscardOldestPolicy:丢弃任务队列里最近的一个任务,并执行当前任务
 - DiscardPolicy:不处理,直接丢掉
 
ThreadPoolExecutor参数总结
线程池中,固定有corePoolSize数量的存活线程,当它们空闲时,需要被执行的任务直接分配给这些线程执行。当这些线程都不可用时(即繁忙状态),新的任务就加入workQueue任务队列,等待被分配给空闲线程执行。当任务队列满了,线程池开始创建新线程来接收任务,直到线程数量达到maximumPoolSize就不再创建。若任务队列满,且线程数满,则新任务既不能添加到队列中,也不能被线程接收,会根据设置的饱和策略RejectedExecutionHandler处理。
线程池种类特点
newFixedThreadPool
1  | public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {  | 
一直拥有固定数量的存活线程的线程池(最大线程数=核心线程数)。
任务阻塞队列是LinkedBlockingQueue(链表阻塞队列),队列长度默认为Integer.MAX_VALUE。
若任务队列满了则使用默认的饱和策略AbortPolicy,直接抛出异常 RejectedExecutionException。
newCachedThreadPool
1  | public static ExecutorService newCachedThreadPool() {  | 
核心线程数=0,最大线程数=Integer.MAX_VALUE,即它的线程数无限制。由于无核心线程,每个线程在空闲 60s 后被销毁。
任务阻塞队列是SynchronousQueue,队列长度默认为Integer.MAX_VALUE。
SynchronousQueue是一个不存储元素的队列,它只负责传递任务给池中线程,且添加任务时必须等待上一次任务已传递出去。可以理解为有一个任务进来时它就“满了”,需要线程池新建线程来接收队列中的任务。
这个线程池因为线程数无限制,所以一般用来执行任务较小的线程,执行完 60s 后即可销毁。若任务数量多且每个任务执行时间较长,则会不断创建线程,占用内存。
newSingleThreadExecutor
1  | public static ExecutorService newSingleThreadExecutor() {  | 
一直有且仅有一个线程的线程池(最大线程数=核心线程数=1),线程空闲后即被销毁。
任务阻塞队列是LinkedBlockingQueue(链表阻塞队列),队列长度默认为Integer.MAX_VALUE。
若任务队列满了则使用默认的饱和策略AbortPolicy,直接抛出异常 RejectedExecutionException。
这个线程池因为只有一个线程,所以可以保证队列中的任务会被顺序执行(队列本身是先进先出的顺序),若这个线程异常结束,会有另一个取代它。
newScheduledThreadPool
1  | public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {  | 
这个线程池的创建被封装了,其实等同于:
1  | new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,  | 
核心线程数=corePoolSize,最大线程数=Integer.MAX_VALUE,即它的线程数无限制。线程空闲后即被销毁。
任务阻塞队列是DelayedWorkQueue(优先级队列),队列长度默认为Integer.MAX_VALUE。
优先级队列的意思是,它会对插入的数据进行优先级排序,保证优先级越高的数据首先被获取,与数据的插入顺序无关。这里的DelayedWorkQueue就是按照时间排序。
DelayedWorkQueue存储的任务元素只能是RunnableScheduledFutures,而上面提到的 LinkedBlockingQueue、SynchronousQueue 的任务只需要是Runnable。
这个线程池提供了schedule和scheduleAtFixedRate方法,可以设置任务的延时执行时间和周期性执行时间,用于需要延迟或周期性执行任务的场景。
newSingleThreadScheduledExecutor
1  | public static ScheduledExecutorService newSingleThreadScheduledExecutor() {  | 
从创建这个线程池的方法中可以看出,它结合了newSingleThreadExecutor和newScheduledThreadPool的特点,只有单线程,任务按优先级队列中的时间顺序执行。
线程池中的线程“活着”的原理
问题:线程池中的线程做完任务后,为什么不会被销毁,而是“活着”等待任务?
ThreadPoolExecutor 借助其成员 Worker worker 间接执行任务线程 thread.run()。Worker 是 ThreadPoolExecutor 的内部类,其中一个属性是Thread thread。同时,worker 本身实现了Runnable,它的 run 方法就是负责执行成员thread.run(),或着等待任务。
当 worker 中的 thread=null 时,会去任务队列中取任务,取到任务则执行任务。取不到任务的情况分两种,一种是活动线程数 < 核心线程数时,worker 会进入阻塞状态,直到能取到任务为止;另一种是活动线程数 > 核心线程数时,worker 会在限定时间内等待任务,若没等到任务,则 worker 线程销毁。
因此,线程池可以保证池中一定有“核心线程数”的线程存在,超过“核心线程数”的线程没有任务就会被销毁。
参考资料: https://blog.csdn.net/anhenzhufeng/article/details/88870374