常用线程池类型
Java通过Executors
静态方法创建4种不同类型线程池。
- newSingleThreadExecutor 创建单例的线程池,保证执行任务顺序,超出线程任务将会在任务中等待,所有的任务都按照FIFO队列顺序执行。
- newFixedThreadPool 创建一个固定大小的线程组,指定工作线程数量,当任务超过指定工作数量时,在队列中排队等待执行。
- newCachedThreadPool 创建一个可以缓存线程池,这个线程池活动线程是0,最大线程Integer.MAX,当不断有新的任务添加到线程池中,池内线程数量不够时,可以立刻创建新的线程执行任务。当空闲的线程超过60s就被系统回收掉。
- newScheduleThreadPool 创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。
- newWorkStealingPool 会创建一个含有足够多线程的线程池,来维持相应的并行级别,它会通过工作窃取的方式,使得多核的 CPU 不会闲置,总会有活着的线程让 CPU 去运行。
像newSingleThreadExecutor、newFixedThreadPool、newCachedThreadPool都时内部封装ThreadPoolExecutor
生成线程池的,下面具体分析ThreadPoolExecutor这个类。
ThreadPoolExecutor 构造函数
1 | public ThreadPoolExecutor(int corePoolSize, |
- corePoolSize 线程核心线程数,不会被回收的线程。
- maximumPoolSize 线程池能够申请最大线程数量
- workQueue 同步性队列转载执行的任务
- keepAliveTime 当线程数大于核心时,这是多余的空闲线程在终止之前等待新任务的最大时间。
- threadFactory 线程工厂
- handler 当任务数量超过队列容量时,需要处理这种情况,饱和策略,主要有4种处理策略
- AbortPolicy:直接抛出异常,这是默认策略;
- CallerRunsPolicy:使用调用者所在的线程来执行任务;
- DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
- DiscardPolicy:直接丢弃任务;
线程池疑问
创建线程池基本核心构造参数我们已经知道了,但是我们还有很多问题没有搞明白的。怎么知道线程池内每个线程运行状态,是在工作中还是空闲呢?是不是有一个专门线程去标记空闲线程活动时间?线程是如何实现共用线程。 带着这些问题去阅读代码。
线程池内线程状态
以下内容都是来自ThreadPoolExecutor
代码注释。
线程池内的线程状态都是有一个AtomicInteger ctl
保持的,是一个原子整数,包装了两个领域含义。
workerCount
有效的线程数 ,线程总数2 ^ 29 -1 ,线程启动数量不包括线程停止的数量,而该值可能是
与活动线程的实际数量暂时不同。例如当ThreadFactory创建线程失败时,线程正在执行退出,统计线程数量依然包括退出的线程。runState
线程状态RUNNING
正在接受新的任务并且处理队列中的任务SHUTDOWN
不接受新的任务,但是能处理任务STOP
不能接受新的任务,不能处理队列中的任务,但是可以中断正在执行的任务。TIDYING
所有的任务终止,workerCount为0 ,线程全部过渡到TIDYING状态,即将运行terminated() 钩子方法TERMINATED
terminated() 钩子方法执行完成
这些状态都有一个转换顺序
RUNNING -> SHUTDOWN
执行shutdown()(RUNNING or SHUTDOWN) -> STOP
执行shutdownNow()SHUTDOWN -> TIDYING
当任务队列和线程池都是空STOP -> TIDYING
线程池都是空TIDYING -> TERMINATED
当 terminated()钩子方法执行完
这些状态具体代码实现1
2
3
4
5
6
7
8
9
10private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// runState is stored in the high-order bits
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
execute 方法解析
1 | public void execute(Runnable command) { |
addWorker()
主要流程检查线程池状态是否合法,创建新的线程,加入workers中,调用start()
执行任务。我们去了解下Worker 类
1 | private final class Worker |
Worker其实就是Runnable包装类,但是增加了任务中断功能,他的主要任务就是维护中断状态,继承AQS可以简化获取和释放围绕每个任务执行的锁定,防止旨在唤醒等待任务的工作线程的中断。
了解Worker怎么执行任务的进入runWorker()
1 | final void runWorker(Worker w) { |
结合Worker构造函数,Worker在初始化就自己给自己上锁了,避免线程在任务还没有开始的情况下就被中断了 。启动线程执行runWorker方法,取出任务,释放锁,如果Worker中的任务为空,从队列中拉取任务。处理线程中断,主要依据第一线程状态已经至少STOP状态,然后清除中断状态,在判断线程没有中断信号了,再发送中断信号。按照作者注释的意思就是当线程池已经在停止过程中,线程应该中断,但是必须双重检查防止关闭过程中竞争发送中继信号。调用run方法执行任务。为什么要上锁执行任务,主要是执行任务过程,必须要获取锁才能中断线程的,但是Worker本身不支持重入锁的,只有在任务开始关闭过程才能中断。
在这里我们终于看到线程共用方式了,通过线程不断从队列中获取任务,然后再进行调用run方法执行任务,当线程退出获取队列循环,线程生命周期就结束了。
geTask()
1 | private Runnable getTask() { |
这里我们知道空闲时间是怎么回收线程的,通过同步性队列poll() + 超时时间知道一个线程在这个时间内没有任务执行,线程池处于空闲状态的,返回null给调用方法,跳出while循环,结束整个线程的生命周期。
进入processWorkerExit()
1 | private void processWorkerExit(Worker w, boolean completedAbruptly) { |
进入 tryTerminate()
在线程池SHUTDOWN状态线程为0和任务队列为空的情况,或者STOP状态核心队列为空情况,线程池状向TIDYING转移,传播关闭池信号。
1 | final void tryTerminate() { |
shutdown()
再去了解下线程池终止方法
1 | public void shutdown() { |
进入interruptIdleWorkers() 怎么中断线程
1 | private void interruptIdleWorkers() { |
处理方法挺简单的,修改线程池状态不要接收新的任务,将works中空闲线程取出发出中断信号。
shutdownNow
1 | public List<Runnable> shutdownNow() { |
shutdownNow 会将队列中还没有来得及处理任务全部删除掉,直接调用tryTerminate()终止线程池生命周期。
总结
现在我们知道线程池内部机制是如何创建线程,共用线程,空闲回收,线程池的生命周期。调用execute()提交任务,如果当前线程池数量小于核心线程数,调用addWorker()创建一个新的线程池去执行任务,否则直接加入到队列中。在addWorker()启动一个线程去不断从队列拉取任务,直到一个队列存活时间没有任务执行或者队列为空,线程才会被回收掉。设置线程池时注意参数设置主要一些细节,核心线程数根据任务情况进行设置,大部分情况下都是核心数在处理任务,只有当任务队列超出容量大小的时候,才会创建新的任务去执行任务。所以在设置最大线程数时,注意设置队列容量大小,如果是Integer.MAX,线程数量永远不会超过核心线程数。只有当任务超出队列容量+线程最大值的情况才会触发饱和策略,根据任务需求选择合适处理方法。