概述
相信如果问你,Java中由几种线程池,你也能很快的回答出四种线程池:
newCachedThreadPool
,newFixedThreadPool
,newSingleThreadExecutor
,newScheduledThreadPool
,那么,你了解过这些线程池的实现原理吗?还记得它们有什么区别吗?跟着笔者复习一下。
线程池的基本参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
corePoolSize:核心线程数量
maximumPoolSize:池内最大线程数量
keepAliveTime:线程存活的时间
unit:存活时间单位
workQueue:阻塞队列
threadFactory:线程工厂类,默认为Executors.defaultThreadFactory()
handler:拒绝策略(比如核心线程已到达最大数量,阻塞队列已满等情况的处理)
newCachedThreadPool
可缓存线程池
public static ExecutorService newCachedThreadPool() {
//池内最大线程数量为:Integer.MAX_VALUE
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
注意:SynchronousQueue
是一个不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素
。并且他支持公平访问队列,属于无界阻塞队列
。
特点:
1、线程数无限制。
2、有空闲线程则复用空闲线程,若无空闲线程则新建线程。
3、一定程序减少频繁创建/销毁线程,减少系统开销。
newFixedThreadPool
固定线程数量的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
//核心线程数量就是总线程数量
//线程不会回收
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
注意:LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列
。
特点:
1、可控制线程最大并发数(同时执行的线程数)。
2、超出的线程会在队列中等待。
newSingleThreadExecutor
单线程化的线程池
public static ExecutorService newSingleThreadExecutor() {
//单个线程的线程池
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点:
1、有且仅有一个工作线程执行任务。
2、所有任务按照指定顺序执行,即遵循队列的入队出队规则。
newScheduledThreadPool
定时执行线程池
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
特点:支持定时以指定周期循环执行任务。
注意:前三种线程池是ThreadPoolExecutor不同配置的实例,最后一种是ScheduledThreadPoolExecutor的实例。
线程池原理
从任务提交的流程角度来看,对于使用线程池的外部来说,线程池的机制是这样的:
1、如果正在运行的线程数 <
coreSize
,马上创建核心线程执行该task
,不排队等待。
2、如果正在运行的线程数 >=coreSize
,把该task
放入阻塞队列(BlockingQueue)
。
3、如果队列已满
且正在运行的线程数 <maximumPoolSize
,创建新的非核心线程执行该task。
4、如果队列已满
且正在运行的线程数 >=maximumPoolSize
,线程池调用handler的reject方法拒绝本次提交
。
简单理解就是:核心线程->阻塞队列->非核心线程->handler拒绝提交。
阻塞队列
阻塞队列在Java
是一种非常常见的数据结构,常用的阻塞队列有七种,在线程池中等场景下较为常见。
有界队列
1、初始的
poolSize < corePoolSize
,提交的runnable
任务,会直接做为new一个Thread
的参数,立马执行 。
2、当提交的任务数超过了corePoolSize
,会将当前的runable
提交到一个BlockQueue
中。
3、有界队列满了之后,如果poolSize < maximumPoolsize
时,会尝试new 一个Thread的进行救急处理
,立马执行对应的runnable任务
。
4、如果3中也无法处理了,就会走到第四步执行reject操作
。
无界队列
与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新的任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加,若后续仍有新的任务加入,而没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存。 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略。
ArrayBlockingQueue
由数组结构组成的有界阻塞队列
。
此队列按照先进先出(FIFO)
的原则对元素进行排序,但是默认情况下不保证线程公平的访问队列
,即如果队列满了,那么被阻塞在外面的线程对队列访问的顺序是不能保证线程公平(即先阻塞,先插入)的
。
LinkedBlockingQueue
一个由链表结构组成的有界阻塞队列
:此队列按照先出先进的原则对元素进行排序
PriorityBlockingQueue
支持优先级的无界阻塞队列
DelayQueue
支持延时获取元素的无界阻塞队列
,即可以指定多久才能从队列中获取当前元素。
SynchronousQueue
不存储元素的阻塞队列,每一个put必须等待一个take操作,否则不能继续添加元素。并且他支持公平访问队列,属于无界阻塞队列。
LinkedTransferQueue
由链表结构组成的无界阻塞队列
TransferQueue。相对于其他阻塞队列,多了tryTransfer和transfer
方法
transfer方法
:如果当前有消费者正在等待接收元素(take或者待时间限制的poll方法),transfer
可以把生产者传入的元素立刻传给消费者。如果没有消费者等待接收元素,则将元素放在队列的tail节点
,并等到该元素被消费者消费了才返回。
tryTransfer方法
:用来试探生产者传入的元素能否直接传给消费者。如果没有消费者在等待,则返回false。和上述方法的区别是该方法无论消费者是否接收,方法立即返回。而transfer方法
是必须等到消费者消费了才返回。
LinkedBlockingDeque
链表结构的双向阻塞队列,优势在于多线程入队时,减少一半的竞争。