Java-并发-线程池
摘要
本文简要介绍线程池,浅析其原理。
0x01 基本概念
1.1 Java线程池简介
Java多线程环境中,使用线程池是是否必要的。在《阿里巴巴Java编码规范v1.4.0》之中,就可以看到以下描述:
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
可以看到,使用线程池主要目的是减少创建和销毁线程的开销,以及线程过度导致CPU过度切换从而使得效率降低的问题。
具体来说,Java中可以创建线程池,然后要用线程时就以submit
(可得到一个Future)方法提交Runnable或Callable或以execute
方法提交Runnable给线程池,由线程池来调度其内部线程执行提交线程的run
方法。
1.2 线程池的创建方式
还是看看《阿里规范》咋说的:
【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
正例:
public class TimerTaskThread extends Thread { public TimerTaskThread() {
super.setName(“TimerTaskThread”);
… }
}
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
以上四类常用的线程池都因请求队列或最大线程数量为Integer.MAX_VALUE
从而可能导致OOM被阿里规范禁止掉了。
所以,我们应该用如下方式创建:
/**
* corePoolSize - 线程池核心池的大小
* maximumPoolSize - 线程池的最大线程数
* keepAliveTime - 设置当线程数大于核心时,多余的空闲线程等待新任务的最长时间
* unit - keepAliveTime的时间单位
* workQueue - 用来储存等待执行任务的队列。
* threadFactory - 线程创建的工厂类实例
* handler - 拒绝策略
*/
BlockingQueue workQueue = new LinkedBlockingDeque<>(10);
ExecutorService executorService = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, workQueue, new ThreadPoolExecutor.AbortPolicy());
注意,超过核心线程后提交的线程会先放入workQueue,放满后再提交才会启用(maximumPoolSize-corePoolSize)的那部分线程来接收新提交的线程。
而如果新提交的线程没有超过等待队列限制,会放入队列。等到某个核心线程运行完自己持有的Runnable的run方法后,以FIFO的方式从等待队列中取等待的Runnable,执行其run方法。
1.3 拒绝策略
当线程池的等待队列中的线程对象已经达到设定的阈值后,再提交线程,会尝试创建非核心线程,如果创建失败,则触发设定的拒绝策略。该拒绝策略的基类是java.util.concurrent.RejectedExecutionHandler
,目前有4种实现方式,他们都是java.util.concurrent.ThreadPoolExecutor
的静态内部类。
1.3.1 AbortPolicy
默认
在队列已满的时候继续提交,抛出RejectedExecutionException。
1.3.2 CallerRunsPolicy
在队列已满的时候继续提交,就把这个线程给提交者所在线程执行。
提交者所在线程会先执行这个提交的线程run方法内容,再继续执行自己代码。
1.3.3 DiscardPolicy
在队列已满的时候继续提交,仅拒绝但不做任何额外操作,如抛异常等。
1.3.4 DiscardOldestPolicy
在队列已满的时候,把队列中排队最久的线程poll出来,并把提交的新线程放进等待队列。
1.4 线程池的使用
一般使用方式示例如下:
- execute Runnable
public class RunnableExecuteTest {
public static void main(String[] args){
ExecutorService es = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10));
es.execute(new Runnable() {
@Override
public void run(){
System.out.println("runnable");
}
});
es.shutdown();
}
}
- submit Runnable
public class RunnableTest {
public static void main(String[] args){
ExecutorService es = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10));
es.submit(new Runnable() {
@Override
public void run(){
System.out.println("runnable");
}
});
es.shutdown();
}
}
- submit Callable
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(10));
Future future = es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
return "Hello chengc";
}
});
System.out.println("callable result =" + future.get());
es.shutdown();
}
}
0x02 实现原理
2.1 概述
第一张学习了线程池的使用方式,这一章我们简单看看线程池内部原理,凭啥他就能复用线程,节约资源。
我们可以先看看他的继承关系:
其实还是很简单的关系,那么我们先看看底层的Executor接口。
2.2 Executor
java.util.concurrent.Executor
public interface Executor {
void execute(Runnable command);
}
这个类很简单,只定义了一个execute方法用来提交Runnable。
2.3 ExecutorService
java.util.concurrent.ExecutorService
public interface ExecutorService extends Executor {
/**
* 开始一个顺序的关闭,之前已经提交的线程任务会被继续执行,但不再接受提交线程任务。
*
* 注意,该方法不会阻塞等待已经提交的线程任务执行完成,用awaitTermination来等待
*
* @throws SecurityException
*/
void shutdown();
/**
* 尝试停止所有存活执行的线程任务,等待运行的任务不再执行,也不再接受新的任务.
* 这里的尝试是指不能保证停止线程,因为该方法的经典实现是发出interrupt,如果
* 目标线程不能正确响应中断请求,就会停止失败。
*
* 注意,该方法不会阻塞等待已经提交的线程任务执行完成,用awaitTermination来等待
*
* @return 等待队列中还未开始执行的Runnable list
* @throws SecurityException
*/
List<Runnable> shutdownNow();
/**
* return true 如果线程池已经关闭
*/
boolean isShutdown();
/**
* 只有当在调用isTerminated方法前调用过shutdown或shutdownNow,
* 才有可能返回true
*
* @return {@code true} 如果所有线程任务在shutdown后已经完成
*/
boolean isTerminated();
/**
* 阻塞当前线程,直到线程池中所有线程任务在shutdown后已经完成执行,
* 或是达到指定的超时时间,或是当前线程被中断,以先发生者为准
*
* @param timeout 等待最大时间
* @param unit 超时时间单位
* @return {@code true} 线程池终止
* {@code false} 线程池在达到超时时间后仍未终止
* @throws InterruptedException 等待时发生中断
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 提交一个带返回值的Callable,返回一个代表该线程执行结果的Future。
* 可使用Future.get方法,在执行完成后返回任务的结果
*
* 如果要立刻阻塞等待提交的线程任务,可使用以下方式
* Future result = exec.submit(aCallable).get();
*
*
* @param task 要提交的线程任务对象
* @param <T> 任务返回值类型的泛型
* @return 一个代表该线程执行结果的Future
* @throws RejectedExecutionException 当不能被提交执行
* @throws NullPointerException task对象为null
*/
<T> Future<T> submit(Callable<T> task);
/**
* 提交一个带返回值的Callable,返回一个代表该线程执行结果的Future。
* 可使用Future.get方法,在执行完成后返回任务的结果
*
* @param task 要提交的线程任务对象
* @param result 需要返回的result对象
* @param <T> 任务返回值类型的泛型
* @return 一个代表该线程执行结果的Future
* @throws RejectedExecutionException 当不能被提交执行
* @throws NullPointerException task对象为null
*/
<T> Future<T> submit(Runnable task, T result);
/**
* 提交一个不带返回值的Runnable,返回一个代表该线程执行结果的Future。
* Future的get方法在成功完成后将返回null
*
* @param task 要提交的线程任务对象
* @return 一个代表该线程执行结果的Future
* @throws RejectedExecutionException 当不能被提交执行
* @throws NullPointerException task对象为null
*/
Future<?> submit(Runnable task);
/**
* 执行多个提交的Callable线程任务,返回多个futrue组成的list,
* 且其中的每个Future对象调用isDone方法都返回true。
* 需要注意的是已完成的任务可能是正常完成也有可能是抛出了异常。
*
* 如果给定的线程任务集合当操作正在执行时被修改,那么返回的结果是undefined
*
* @param tasks 线程任务集合
* @param <T> 线程任务返回值类型
* @return 一个代表线程任务直接返回结果的Future list,顺序和传入的callable集合的顺序相同
* @throws InterruptedException 等待时被中断,此场景下未完成的线程任务被取消
* @throws NullPointerException if tasks or any of its elements are {@code null}
* @throws RejectedExecutionException 如果有线程任务不能被调度执行
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException;
/**
* 与上面方法基本相同,不同的是加入了等待超时时间
* 如果达到了超时时间还没完成,任务就会被取消
* 当所有任务已经完成或timeout时间达到,就会返回(以先发生为准)
*
* 在返回时,未完成的任务将被取消。
*
* 如果给定的线程任务集合当操作正在执行时被修改,那么返回的结果是undefined
*
* @param tasks 线程任务集合
* @param <T> 线程任务返回值类型
* @param timeout 等待最大时间
* @param unit 等待最大时间单位
* @return 一个代表线程任务直接返回结果的Future list,
* 顺序和传入的callable集合的顺序相同。
* 如果发生超时,某些线程任务可能未完成
* @throws InterruptedException 等待时被中断,此场景下未完成的线程任务被取消
* @throws NullPointerException if tasks, any of its elements, or
* unit are {@code null}
* @throws RejectedExecutionException 如果有线程任务不能被调度执行
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException;
/**
* 执行给定的集合内的线程任务,返回最早的那一个已经成功执行完的线程的结果
* 这里不会返回那些因为抛出异常导致的结束的线程的结果。
*
* 在正常或异常返回时,未完成的任务将被取消。
*
* 如果给定的线程任务集合当操作正在执行时被修改,那么返回的结果是undefined
*
* @param tasks 线程任务集合
* @param <T> 线程任务返回值类型
* @return 最先成功执行完成的某个任务结果
* @throws InterruptedException 等待时被中断,此场景下未完成的线程任务被取消
* @throws NullPointerException if tasks or any element task
* subject to execution is {@code null}
* @throws IllegalArgumentException tasks参数为空
* @throws ExecutionException 没有任务成功执行完成
* @throws RejectedExecutionException 如果有线程任务不能被调度执行
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException;
/**
* 执行给定的集合内的线程任务,返回最早的那一个已经成功执行完的线程的结果
* 这里不会返回那些因为抛出异常导致的结束的线程的结果。
* 成功返回的前提是在设定的timeout内完成
*
* 在正常或异常返回时,未完成的任务将被取消。
*
* 如果给定的线程任务集合当操作正在执行时被修改,那么返回的结果是undefined
*
* @param tasks 线程任务集合
* @param <T> 线程任务返回值类型
* @param timeout 等待最大时间
* @param unit 等待最大时间单位
* @return 最先成功执行完成的某个任务结果
* @throws InterruptedException 等待时被中断,此场景下未完成的线程任务被取消
* @throws NullPointerException if tasks, or unit, or any element
* task subject to execution is {@code null}
* @throws TimeoutException timeout耗尽,仍没有任务成功执行完成
* @throws ExecutionException 没有任务成功执行完成
* @throws RejectedExecutionException 如果有线程任务不能被调度执行
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
2.4 AbstractExecutorService
java.util.concurrent.AbstractExecutorService
public abstract class AbstractExecutorService implements ExecutorService
这个类是抽象类,实现了ExecutorService
接口,有一些方法会被直接调用,我们挑选一些最终要的分析。
2.4.1 submit
- submit(Runnable task)
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
- newTaskFor
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
可以看到,newTaskFor
方法创建了FutureTask
2.5 ThreadPoolExecutor
java.util.concurrent.ThreadPoolExecutor
2.5.1 重要属性
-
ctl
private static int ctlOf(int rs, int wc) { return rs | wc; } private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
这个属性是线程池的主控状态,包含两个概念域:
- workerCount(低29位)
表示有效的线程数量,具体来说是允许启动但不允许停止的worker线程数量。。最大值为(2^29)-1,约5亿。 - runState(高3位)
表示线程池生命周期状态,如运行中、正在关闭等。有以下值:
private static final int COUNT_BITS = Integer.SIZE - 3; // 可接收新任务,要处理排队任务 private static final int RUNNING = -1 << COUNT_BITS; // 不接收新任务,但要处理排队任务 private static final int SHUTDOWN = 0 << COUNT_BITS; // 不接收新任务,不处理排队任务,且对正在执行的任务发出中断 private static final int STOP = 1 << COUNT_BITS; // 所有任务都已终止,且workerCount=0。 // 过度到TIDYING状态会执行terminated()方法 private static final int TIDYING = 2 << COUNT_BITS; // terminated状态执行完毕。 // 调用awaitTermination等待终止的线程会在到达TERMINATED状态后返回 private static final int TERMINATED = 3 << COUNT_BITS;
可以看到,runState的状态都在高位表示。需要注意的是,runState单调增加,但不是依次命中所有状态。状态转移图如下:
- workerCount(低29位)
-
CAPACITY
// 2^29-1,即二进制的29个1
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
- workQueue
线程池等待队列
private final BlockingQueue<Runnable> workQueue;
2.5.2 内部类
2.5.2.1 Worker
Worker就是线程池内的工作worker,内部封装了Thread,复用来执行提交的Runnable的run
方法。
-
类定义
Worker实现自Runnable。有趣的是,他还继承了AQS!那就很自然的想到,是不是他也用了AQS的state
那套东西。关于AQS,可以看这篇文章AQS原理浅析private final class Worker extends AbstractQueuedSynchronizer implements Runnable
-
重要属性
// worker持有的Thread对象。当线程工厂创建失败时为null final Thread thread; // 初始线程任务,可能是null Runnable firstTask; // 已完成的任务计数器 volatile long completedTasks;
-
构造方法
构造方法, 使用ThreadFactory创建Thread。注意这里就把worker实例作为tagetRunnable传给了新创建的Thread。firstTask可能为null。
Worker(Runnable firstTask) { setState(-1); // inhibit interrupts until runWorker this.firstTask = firstTask; this.thread = getThreadFactory().newThread(this); }
-
run
这个就是执行thread.start()时会调用的runnable(也就是我们的worker)的run方法// 调用ThreadPoolExecutor.runWorker方法来启动worker public void run() { runWorker(this); }
-
其他部分代码如下
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
// 判断是否为1即锁状态,0为未锁定状态
protected boolean isHeldExclusively() {
return getState() != 0;
}
// 尝试获取锁
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 尝试释放锁
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 以下为关于锁的一系列方法
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
2.5.3 线程池重要方法
-
execute
该方法用来提交任务。- 如果worker数量小于核心线程池数量
尝试创建新的核心线程worker,并将目标task作为firstTask提交给该worker执行 - 如果worker数量已经>=核心线程数或addWorker创建线程失败
- 如果线程池处于Running状态且调用
workQueue.offer
成功将任务放入等待队列
此时需要再次检查线程池状态,若已经Stop则拒绝此次任务提交;否则继续判断重新检测到worker的数量是否为0,若为0则新增一个非核心线程worker。 - 其他情况需要再次尝试增加worker
- 此时可能的情况是线程池已经处于非Running状态,则addWorker会失败,触发reject事件
- 也可能是线程池处于Running,但workQueue已满无法offer task。
这种情况就按配置的maximumPoolSize创建新非核心线程worker,并将目标task作为firstTask提交给该worker执行。如果还是无法创建线程或创建线程失败或线程池shutdown/stop,触发reject事件
- 如果线程池处于Running状态且调用
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); /* * Proceed in 3 steps: * * 1. If fewer than corePoolSize threads are running, try to * start a new thread with the given command as its first * task. The call to addWorker atomically checks runState and * workerCount, and so prevents false alarms that would add * threads when it shouldn't, by returning false. * * 2. If a task can be successfully queued, then we still need * to double-check whether we should have added a thread * (because existing ones died since last checking) or that * the pool shut down since entry into this method. So we * recheck state and if necessary roll back the enqueuing if * stopped, or start a new thread if there are none. * * 3. If we cannot queue task, then we try to add a new * thread. If it fails, we know we are shut down or saturated * and so reject the task. */ // 得到主控ctl的数值 int c = ctl.get(); if (workerCountOf(c) < corePoolSize) { // worker数量小于核心线程池数量, // 尝试创建新的核心线程worker, // 并将目标task作为firstTask提交给该worker if (addWorker(command, true)) // 如果创建worker成功,且将任务提交给worker成功,就直接返回了 return; // 否则创建线程失败或线程池shutdown/stop,获取最新主控ctl c = ctl.get(); } // 走到这一步,worker数量已经>=核心线程数或addWorker创建线程失败 if (isRunning(c) && workQueue.offer(command)) { // 线程池处于Running状态且成功放入等待队列 // 再次获取最新主控ctl int recheck = ctl.get(); if (! isRunning(recheck) && remove(command)) // 如果此时不再是Running状态且成功从等待队列移除线程任务 // 就直接调用拒绝策略类对象的rejectedExecution方法 reject(command); else if (workerCountOf(recheck) == 0) // 重新检测到worker的数量为0 // 新增非核心线程worker,注意此时当前task已经放入workQueue addWorker(null, false); } // 否则再次尝试增加worker // 1.此时可能的情况是非Running,则addWorker失败返回false,触发reject // 2.也可能是Running,但workQueue已满, // 按maximumPoolSize创建新worker,并将目标task作为firstTask提交给该worker else if (!addWorker(command, false)) // 创建线程失败或线程池shutdown/stop,触发reject reject(command); }
- 如果worker数量小于核心线程池数量
-
addWorker
线程池创建worker,且按需分配task
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
// 主控状态
int c = ctl.get();
// 线程池运行状态
int rs = runStateOf(c);
// Check if queue empty only if necessary.
// 如果线程池状态非RUNNING,
// 且不是(SHUTDOWN状态&&firtstTask为空&&workQueue不为空)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
// 就返回false,代表创建worker失败
return false;
for (;;) {
// worker数
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
// worker数超过最大值或超过指定类型阈值,就返回false
return false;
if (compareAndIncrementWorkerCount(c))
// CAS方式将workerCount+1,成功就就跳出最外层循环
break retry;
// CAS增加workerCount失败,说明有其他线程也在提交,重读ctl
c = ctl.get();
if (runStateOf(c) != rs)
// runState已经改变,重新最外层循环
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 到一步,已经成功增加了workerCount
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
// 创建一个Worker,并将目标task作为其firstTask
w = new Worker(firstTask);
// 获取w内部包装的Thread对象
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
// 持有锁后,再次检查runState
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 线程池RUNNING状态或SHUTDOWN但firstTask为null
// 就添加该worker到workers集合
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
// worker创建并添加成功
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// worker添加成功,启动thread线程,会执行worker的run方法
// 然后真正执行ThreadPoolExecutor.runWorker方法
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
-
reject
调用注册的拒绝策略类的rejectedExecution方法final void reject(Runnable command) { handler.rejectedExecution(command, this); }
比如默认的AbortPolicy如下:
public static class AbortPolicy implements RejectedExecutionHandler { public AbortPolicy() { } /** * 该拒绝策略就是抛出RejectedExecutionException. * * @param r the runnable task requested to be executed * @param e the executor attempting to execute this task * @throws RejectedExecutionException always */ public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); } }
-
runWorker
该方法由Worker线程run
方法调用。主要就是从firstTask开始执行其run方法,随后调用workQueue.poll(withTimeout)或workQueue.take方法获取task来执行run方法,达到线程复用的目的:final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { // 如果firstTask不为空就先执行他,否则循环从workQueue中获取task来执行他的run方法 // 一般来说是有任务等待超时(timedOut)且wc>corePoolSize // 且wc>1或workQueue为空时,需要减少一个worker,这里getTask就返回null了 // 会退出while循环 while (task != null || (task = getTask()) != null) { // 对Worker进行了加锁,保证线程安全 w.lock(); // If pool is stopping, ensure thread is interrupted; // if not, ensure thread is not interrupted. This // requires a recheck in second case to deal with // shutdownNow race while clearing interrupt if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { // 由task的Thread线程执行该前置方法,默认是空实现 beforeExecute(wt, task); Throwable thrown = null; try { // 执行目标task的run方法 task.run(); } catch (RuntimeException x) { thrown = x; throw x; } catch (Error x) { thrown = x; throw x; } catch (Throwable x) { thrown = x; throw new Error(x); } finally { // 后置默认实现也是空 afterExecute(task, thrown); } } finally { task = null; // 该worker已完成的任务数累加 w.completedTasks++; // 解锁 w.unlock(); } } // 非异常退出,也就是说是非核心线程等待的空闲时间已到 completedAbruptly = false; } finally { // 将worker退出 processWorkerExit(w, completedAbruptly); } }
-
getTask
- 调用workQueue.poll(withTimeout)或workQueue.take方法获取task。
- 如果等待超时后仍未获取到task,则判断目前的Worker线程数是否超过核心线程数:
- Worker线程数超过核心线程数,且worker线程数>1(一般是这种情况)或task队列为空则返回null,该worker线程会被调用processWorkerExit方法退出
- Worker线程数未超过核心线程数说明本线程为核心线程,那就继续尝试从workQueue队列中获取task
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out? for (;;) { int c = ctl.get(); int rs = runStateOf(c); // 检测runState,非RUNNING且满足特定条件就清空worker计数 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } // worker数量 int wc = workerCountOf(c); // allowCoreThreadTimeOut默认是false, // 代表不允许核心线程空闲时按keepAliveTime来超时死掉 // 那么默认情况下,timed只有在workCount超出corePoolSize时为true // 否则就是false boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 一般来说是有任务等待超时(timedOut)且wc>corePoolSize // 且wc>1或workQueue为空时,将WorkerCount减一,并返回null // 此时该worker线程会被调用processWorkerExit方法退出 if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 1.wc > corePoolSize时,r=workQueue.poll(keepAliveTime) // 也就是说,最多等待keepAliveTime来获取任务 // 2.否则,就是wc<=corePoolSize,r=workQueue.take, // 即无任务时永久等待直到获取到任务 Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) // 获取到了Runnable任务就返回 return r; // 否则说明是超时了未获取到任务,置timedOut为true timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
-
processWorkerExit
该方法处理worker线程退出:private void processWorkerExit(Worker w, boolean completedAbruptly) { // 如果是异常退出,则减少worker数量 // 正常退出时因为已经在getTask方法中减少过worker数量了,所以这里不做操作 if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // 累加线程池已完成任务数 completedTaskCount += w.completedTasks; // 从workers容器中移除该worker workers.remove(w); } finally { mainLock.unlock(); } // 判断集群状态, // 如果处于SHUTDOWN且task队列为空(说明调用了shutDown方法且已经没有任务执行) // 或STOP且线程已经为空(说明调用了shutDown或shutDownNow方法,且worker线程都已经退出或没有生成) // 则将线程池状态改为TERMINATED tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { // RUNNING/SHUTDOWN if (!completedAbruptly) { // 正常退出 // 默认min应该是corePoolSize int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && ! workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } // 走到这一步,要么是worker线程异常退出,需要补一个worker线程 // 要么是正常退出,但wroker线程数低于最小阈值,需要补足 addWorker(null, false); } }
2.5.3 辅助方法
-
runStateOf
该方法用来提出线程池状态:// 取高3位的值,就是runState private static int runStateOf(int c) { return c & ~CAPACITY; }
-
workerCountOf
该方法用来提出worker的数量:// 这里的c就是前文提到的主控数字ctl // CAPACITY是29个1 // 所以这就是取低29位,即workerCount private static int workerCountOf(int c) { return c & CAPACITY; }
-
remove
将目标任务从线程池等待队列中删除,所以如果该任务尚未被执行就不会再被执行。public boolean remove(Runnable task) { // 将目标任务从线程池等待队列中删除 boolean removed = workQueue.remove(task); // 尝试终止 tryTerminate(); // In case SHUTDOWN and now empty // 返回移除结果 return removed; }
0x03 总结
3.1 使用
-
使用shutDown开始一个顺序的关闭,继续执行在运行和已提交到queue内的任务,但不再接受提交线程任务。注意,该方法不会阻塞等待已经提交的线程任务执行完成,用awaitTermination来等待
-
使用shutDownNow尝试停止所有存活执行的线程任务,等待运行的任务不再执行,也不再接受新的任务。注意,这里的尝试是指不能保证停止线程,因为该方法的经典实现是发出interrupt,如果目标线程不能正确响应中断请求,就会停止失败。
该方法同样不会阻塞等待已经提交的线程任务执行完成,用awaitTermination来等待
-
submit提交线程任务会构建RunnableFuture,返回一个future,用以得到线程任务执行之后返回值。会调用execute
-
execute直接执行,无返回值
3.2 任务提交
- 如果少于corePoolSize个worker线程在运行,就尝试启动新的Thread线程,并将提交的线程任务作为firstTask。
- 如果>=corePoolSize个worker线程在运行,就将线程任务入队到workQueue
- 如果workQueue已满,就尝试开启新Thread线程来运行该任务。如果失败,就代表线程池已处于shutdown状态或者已经达到maximumPoolSize,此时就触发rejectHandler的reject方法,如抛出
RejectedExecutionException
等。
3.3 线程复用
worker线程会反复从workQueue中取线程任务来执行
3.4 空闲线程超时
超过corePoolSize的worker会以keepAliveTime从workQueue poll任务,如果没拿到,说明超时,该线程处理一系列收尾工作后就会结束
0x04 常用线程池
4.1 Executors.newCachedThreadPool
4.1.1 概述
该方法实现如下:
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>())
其实就是创建了一个corePoolSize为0,但maximumPoolSize为Integer.MAX_VALUE大的,拥有1分钟空闲超时的,使用SynchronousQueue作为task等待队列的线程池。
这个线程池很有意思,task submit到该线程池的时候,因为corePoolSize为0,所以会调用SynchronousQueue的workQueue.offer
方法提交task,但高概率每次返回false,从而导致每次都新建一个非核心线程来执行任务。
一句话对于新提交的线程任务,如果有空闲的非核心Worker线程正在poll(withTimeout)等任务,则会拿出任务来执行其run方法;否则新建一个非核心worker线程来处理任务
。
所以说可能会创建大量的线程,从而导致 OOM。
4.1.2 关于SynchronousQueue
在Java-并发-SynchronousQueue一文中已经说过,SynchronousQueue并不能算真正意义上的队列,而可以说是一种管道,直接将资源在线程之间交互,类似我们生活中的一手交钱一手交货。
所以这里task在添加时,workQueue.offer
很大概率返回false,因为一般来说没有这么巧刚好空闲的、存活的非核心线程在poll(withTimeout)等待,如果有则该worker拿到task开始执行,并返回true。否则,返回false,随后就新增一个非核心worker线程,处理task。
所以,前文会说CachedThreadPool的问题在于可能会创建大量的线程,从而导致 OOM。
4.2 Executors.newScheduledThreadPool(N)
4.2.1 概述
该方法实原理现如下:
new ThreadPoolExecutor(NcorePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue())
即创建了一个corePoolSize为N, maximumPoolSize为Integer.MAX_VALUE的线程池。使用DelayedWorkQueue作为task等待队列。
其内部task为ScheduledFutureTask,继承或实现了Future/Runnable/Delay等,ScheduledThreadPoolExecutor是通过DelayedWorkQueue优先级判定规则来以先后顺序执行任务的。
4.2.2 scheduleAtFixedRate
4.2.2.1 概述
- scheduleAtFixedRate是以上一个任务开始时间计时
- 就算提前执行完了也要从上一个任务开始时计算,等到下一个相对时间点才开始执行
4.2.2.2 例1
任务执行时间(1s)小于间隔时间(5s),表现为每个任务执行开始后5秒下一个任务即开始执行:
public static void test1(){
scheduler.scheduleAtFixedRate(() -> {
logger.info("Start: scheduleAtFixedRate" );
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("End : scheduleAtFixedRat");
}, 0, 5 , SECONDS);
}
输出如下:
2019-11-08 11:45:42.744 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,35 - Start: scheduleAtFixedRate
2019-11-08 11:45:43.751 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,41 - End : scheduleAtFixedRat
2019-11-08 11:45:47.748 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,35 - Start: scheduleAtFixedRate
2019-11-08 11:45:48.752 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,41 - End : scheduleAtFixedRat
2019-11-08 11:45:52.745 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,35 - Start: scheduleAtFixedRate
2019-11-08 11:45:53.746 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,41 - End : scheduleAtFixedRate
可以看到每五秒执行一次任务。
4.2.2.2 例2
本例任务执行时间(4s)大于间隔时间(2s),此时下一个任务会等待之前的任务执行完,再立刻开始执行。
public static void test2(){
scheduler.scheduleAtFixedRate(() -> {
logger.info("Start: scheduleAtFixedRate" );
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("End : scheduleAtFixedRat");
}, 0, 2 , SECONDS);
}
输出如下:
2019-11-08 11:51:21.435 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,52 - Start: scheduleAtFixedRate
2019-11-08 11:51:25.440 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,58 - End : scheduleAtFixedRat
2019-11-08 11:51:25.444 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,52 - Start: scheduleAtFixedRate
2019-11-08 11:51:29.445 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,58 - End : scheduleAtFixedRat
2019-11-08 11:51:29.446 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,52 - Start: scheduleAtFixedRate
2019-11-08 11:51:33.451 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,58 - End : scheduleAtFixedRat
可以看到,每个任务执行时长4秒。虽然调度间隔为2秒,但下一个任务必须等待上一个任务完成后才开始运行。
4.2.3 scheduleWithFixedDelay
4.2.3.1 概述
- scheduleWithFixedDelay以上一个任务结束时间计时
4.2.3.2 例1
任务执行时间(1s)小于间隔时间(5s),表现为每个任务执行完成后5秒下一个任务才开始执行:
public static void test3(){
scheduler.scheduleWithFixedDelay(() -> {
logger.info("Start: scheduleWithFixedDelay" );
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("End : scheduleWithFixedDelay");
}, 0, 5 , SECONDS);
}
输出如下:
2019-11-08 11:59:01.861 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,70 - Start: scheduleWithFixedDelay
2019-11-08 11:59:02.868 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,76 - End : scheduleWithFixedDelay
2019-11-08 11:59:07.873 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,70 - Start: scheduleWithFixedDelay
2019-11-08 11:59:08.876 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,76 - End : scheduleWithFixedDelay
2019-11-08 11:59:13.879 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,70 - Start: scheduleWithFixedDelay
2019-11-08 11:59:14.885 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,76 - End : scheduleWithFixedDelay
可以看到虽然任务1秒就执行完了,但还是要等待指定的间隔时间5秒才开始下一个任务。
4.2.3.2 例2
本例任务执行时间(4s)大于间隔时间(2s),此时下一个任务会等待之前的任务执行完,再等待指定的间隔时间2秒,也就是说一共等了6秒才开始执行。
public static void test4(){
scheduler.scheduleWithFixedDelay(() -> {
logger.info("Start: scheduleWithFixedDelay" );
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("End : scheduleWithFixedDelay");
}, 0, 2 , SECONDS);
}
输出如下:
2019-11-08 12:03:14.786 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,88 - Start: scheduleWithFixedDelay
2019-11-08 12:03:18.794 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,94 - End : scheduleWithFixedDelay
2019-11-08 12:03:20.799 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,88 - Start: scheduleWithFixedDelay
2019-11-08 12:03:24.799 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,94 - End : scheduleWithFixedDelay
2019-11-08 12:03:26.801 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,88 - Start: scheduleWithFixedDelay
2019-11-08 12:03:30.805 INFO [pool-1-thread-1] concurrent.thread.executorservice.scheduled.ScheduleTest,94 - End : scheduleWithFixedDelay
4.3 Executors.newFixedThreadPool(N)
该方法实现如下:
new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
其实就是创建了一个corePoolSize和maximumPoolSize都为N,无空闲超时的(本来就不会有非核心线程),使用无界LinkedBlockingQueue作为task等待队列的线程池。
task submit到该线程池的时候,如果核心线程池已经占满,就会放入无界的LinkedBlockingQueue。
如果放入过多task,可能出现问题。
4.4 Executors.newSingleThreadExecutor
该方法实现如下:
new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory)
很显然该方法就是FixedThreadPool的特例,只允许一个worker线程。
0xFF 参考文档
《阿里巴巴Java编码规范v1.4.0》