类关系图
基本属性:COUNT_BITS、CAPACITY
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
示意图:
线程池状态
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;
状态示意图
若对数据是如何存放不了解的,可以参考如下文章
https://mp-new.csdn.net/mp_blog/creation/editor/117792529
ctl
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
由此可见,ctl初始值如下:
再来分析下 runStateOf
//获取状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
再来分析下 workerCountOf
//获取工作线程数
private static int workerCountOf(int c) { return c & CAPACITY; }
最大值为 2^29 - 1
线程池的其他关键属性
//任务队列
private final BlockingQueue<Runnable> workQueue;
//操作线程池锁
private final ReentrantLock mainLock = new ReentrantLock();
//工作线程
private final HashSet<Worker> workers = new HashSet<Worker>();
//记录整个线程池生命周期中,创建工作线程的最大值
private int largestPoolSize;
//记录整个线程池生命周期中,完成的任务
private long completedTaskCount;
//拒绝策略
private volatile RejectedExecutionHandler handler;
//允许线程存活时间
private volatile long keepAliveTime;
//允许核心线程超时
private volatile boolean allowCoreThreadTimeOut;
//核心线程数
private volatile int corePoolSize;
//允许最大创建的线程数
private volatile int maximumPoolSize;
提交任务方式
1.提交Callable任务
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
2.提交Runnable任务,带返回值
public <T> Future<T> submit(Runnable task, T result) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task, result);
execute(ftask);
return ftask;
}
3.提交Runnable任务,不带返回值
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
由此可见,通过submit提交的任务,最终都会包装为FutureTask,有关FutureTask的分析,可以阅读另外一篇文章。
https://mp-new.csdn.net/mp_blog/creation/editor/117748379
核心方法execute(ftask)
假设核心线程数大小为5,允许创建的最大线程数为10,任务队列大小为8,一次提交了30个任务,执行过程大致如下:
先创建5个核心线程执行任务,再将8个任务放到任务队列中,再创建5个非核心线程执行任务,剩下的12个任务,就会根据拒绝策略,做相应处理,如:抛异常。
备注:例举具体数量,只为更直观理解线程池执行任务的原理,真实情况,并非如此,无需较真。
示意图:
java.util.concurrent.ThreadPoolExecutor#execute
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
//当前线程少于核心线程数,则新开启一个线程
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
//超过核心线程,则将任务添加到workQueue中
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
//再次校验状态,非RUNNING,则移除节点,并执行拒绝策略
if (! isRunning(recheck) && remove(command))
reject(command);
//线程数量为0,则开启一个非核心线程
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
//workQueue满了,则尝试启动非核心线程(maximumPoolSize),失败则执行拒绝策略
//若线程池非RUNNING状态,也会进入此逻辑
else if (!addWorker(command, false))
reject(command);
}
由此可见,执行任务优先顺序为:1.核心线程>2.任务队列>3.非核心线程>4.拒绝策略
现在分析addWorker方法
java.util.concurrent.ThreadPoolExecutor#addWorker
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//超过最大容量,超过核心线程池大小,或者超过最大线程池大小
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//workCount++,成功则跳出整个循环
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//线程池状态变更而导致失败,重新走第一重for循环
if (runStateOf(c) != rs)
continue retry;
//其他线程修改workCount数量而导致cas失败,则重新走第二重for循环
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);//创建worker节点
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // 检查线程状态
throw new IllegalThreadStateException();
workers.add(w);//添加work节点
int s = workers.size();
//更新最大线程数
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//启动worker
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
代码块分析
根据线程池状态可知,
i).若线程池为 RUNNING状态,则case 1就不满足,直接结束if判断,走后续逻辑;
i).若为 STOP 、TIDYING 、TERMINATED状态,则case 2不满足,直接结束if判断,并返回false,addWork失败;
因此,关键分析状态为shutdown的情况。
ii).当线程池处于SHUTDOWN时,若firstTask不为空,case 3不满足,结束if判断,并返回false,addWork失败。由此可见,SHUTDOWN状态不会再接收新任务;
ii).当线程池处于SHUTDOWN,且firstTask为null时
a)若workQueue为空,即没有要执行的任务,没必要再执行addWorker逻辑;
b)若workQueue不为空,说明还有未执行完的任务,此时可以继续addWorker.可见SHUTDOWN状态时,可以继续执行已添加的任务;
现在分析run方法
java.util.concurrent.ThreadPoolExecutor.Worker#run
public void run() {
runWorker(this);
}
分析java.util.concurrent.ThreadPoolExecutor#runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//从workQueue获取任务并执行
while (task != null || (task = getTask()) != null) {
w.lock();
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
//空方法
beforeExecute(wt, task);
Throwable thrown = null;
try {
//手动执行run()方法 非线程start()调用
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;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
//收尾工作
processWorkerExit(w, completedAbruptly);
}
}
现在分析getTask方法
java.util.concurrent.ThreadPoolExecutor#getTask
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 线程池状态>=STOP且workQueue中没有要执行的任务,则直接回收当前线程
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
//非核心线程 或 核心线程允许超时,没取到任务,就会回收该线程
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
//poll方法不会park,或只park指定时间,超时后仍未取到元素,直接返回null
//take会一直park,直到put或offer操作添加元素后,才会唤醒线程
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
//超时未取到元素
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
由上述分析可知,线程池执行任务的大致流程为:
1.客户端提交任务。
2.创建核心线程执行任务;若任务量超过核心线程数,则将超出部分添加到workQueue,若workQueue超出容量,则创建非核心线程执行任务。
3.工作线程执行完当前任务后,从workQueue获取任务并执行。
4.当workQueue中的所有任务全部执行完毕后,回收非核心线程。核心线程则因为getTask方法中的take方法,而被park阻塞,等待新任务提交后才会unpark被唤醒。若允许核心线程超时,则核心线程也会被回收。
主体流程基本分析完毕,现在开始分析shutdown等相关方法。
java.util.concurrent.ThreadPoolExecutor#shutdown
public void shutdown() {
//获取线程池操作锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//校验线程权限
checkShutdownAccess();
//将状态置为 SHUTDOWN
advanceRunState(SHUTDOWN);
//中断 空闲工作线程
interruptIdleWorkers();
onShutdown();//空方法
} finally {
//是否线程池操作锁
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
}
现在分析 interruptIdleWorkers,从方法名可得知,是中断空闲线程
java.util.concurrent.ThreadPoolExecutor#interruptIdleWorkers()
private void interruptIdleWorkers(boolean onlyOne) {
//获取线程池锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//获取工作线程锁
if (!t.isInterrupted() && w.tryLock()) {
try {
//中断
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
空闲线程是如何体现出来的呢?
正是通过w.tryLock(),体现的,中断前,先要获取worker锁,若该线程处于运行状态,获取锁是不可能成功的。因此,只有当worker线程处于空闲状态时,才可能被shutdown。
现在再分析下shutdownNow方法
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
//获取线程池操作锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//将线程池状态设置为 STOP
advanceRunState(STOP);
//中断线程
interruptWorkers();
//清空workQueue
tasks = drainQueue();
} finally {
mainLock.unlock();
}
//尝试终止线程池
tryTerminate();
return tasks;
}
现在分析interruptWorkers
java.util.concurrent.ThreadPoolExecutor#interruptWorkers
private void interruptWorkers() {
//获取线程池操作锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//无需获取worker锁,直接中断
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
通过对比,可发现shutdown 于shutdownNow的区别
shutdown | shutdownNow | |
状态 | SHUTDOWN | STOP |
中断worker线程对象 | 空闲状态的线程 | 所有state>0的线程 |
任务队列workQueue | 不再接收新任务,存量任务可继续执行 | 清空workQueue |
接下来,再分析下worker线程的AQS状态问题
Worker类继承结构如下:
一般AQS的state状态,初始值都为0,可Worker类,初始化时,状态为-1,这肯定有原因的。
Worker线程start后,调用runWorker方法,通过w.unlock(),将状态设置为0,并且注释说明为,允许中断,由此可见,线程在创建到runWorker这段时间内,是不能被中断的。
不能被中断是如何实现的呢?接着往下分析,通过shutdown方法中断线程时,需先获取锁,trylock时,状态必须为0,由此可见,状态未置为0时,是不能中断的。
如下图:shutdownNow也只中断state>0的线程
从这方法,也能分析出,worker锁,是不允许重入的,原因如下:
一旦获取锁成功,state状态就改为1,想再次获取锁时,cas操作必定失败,因此不支持重入。unused这个变量命名,就初露端倪了。而且源码中也确实没有重入的场景。
对比ReentrantLock就更清楚了
ReentrantLock是允许重入的,具体就体现在,更改state时,若当前线程已获取了锁,直接修改获取锁的次数就OK了。
对比也能分析出,Worker获取锁,是非公平模式。至于原因,其实也很简单,从源码分析,可得知,每个Worker都是new出来的,所以worker之间的锁都是相互独立的,worker主要用于执行task,每个worker一次只会执行一个task,由此得知,worker锁,基本是不存在竞争的。何时才会产生竞争呢? 那就是调用shutdown、shutdownNow等方法时,线程Thread1执行shutdown需要获取worker锁,worker线程Thread2执行task任务需要获取worker锁,因此竞争就产生了。从这分析能得出,worker锁基本无竞争,或者竞争很少,因此worker锁中的CLH同步队列基本处于null状态,对于这样的情况,直接尝试获取锁就OK了,没必要用公平锁。
调用链&分析如下:
java.util.concurrent.ThreadPoolExecutor#runWorker
->w.lock();
->java.util.concurrent.ThreadPoolExecutor.Worker#lock
->java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
->java.util.concurrent.ThreadPoolExecutor.Worker#tryAcquire
protected boolean tryAcquire(int unused) {
//直接尝试修改&占用锁,不会判断是否有其他线程
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
最后再分析下线程池中的锁资源。
线程池中,涉及的锁资源主要有如下几种
1.线程池操作全局锁 ReentrantLock mainLock = new ReentrantLock()。
示意图中,用ThreadPool.mainLock表示;
2.线程池任务队列 BlockingQueue<Runnable> workQueue,获取到workQueue的锁资源才能对队列做相关操作,以ArrayBlockingQueue为例,定义了 ReentrantLock lock。
示意图中,用workQueue.lock表示;
3.Worker锁,Worker继承至AQS,自带CLH同步阻塞队列,Worker的run操作,interrupt操作,需要tryLock成功,才能进行,每个Worker都是new出来的,因此有Worker之间的锁是相互独立的。示意图中,用Worker.lock表示;
从示意图分析,基本可以得出如下锁资源竞争的场景:
1.多个用户端执行submit操作,此时需要竞争到ThreadPool.mainLock才能操作成功,竞争失败的,在ThreadPool.mainLock对应的同步队列中等待,直到竞争成功的线程释放锁后,再唤醒一个线程继续争抢资源。
2.addWorker成功后,开始执行firstTask,执行任务无需获取ThreadPool.mainLock,只要Worker.lock即可。每个Worker都有各自独立的Worker.lock,因此相安无事。
3.firstTask执行完毕后,需要从workQueue中获取task,因此需要获取workQueue.lock才能取得task,竞争锁失败的,在workQueue.lock对应的同步队列中等待。倘若此时workQueue为空,核心队列就会被park,并在workQueue的条件队列中等待,有新的task提交时,才会被unpark唤醒,并转移到同步队列中。非核心队列,或者允许核心队列超时,该Worker就直接game over了。
4.用户端执行shutdown操作,尝试中断workers中的工作线程,因此需要获取ThreadPool.mainLock,shutdown只会中断空闲线程,而且需要获取Worker.lock才能操作。
5.用户端执行shutdownNow操作,尝试中断workers中的工作线程,因此需要获取ThreadPool.mainLock,shutdownNow会清空workQueue,因此需要获取workQueue.lock。
-------------------------------------------------------华丽的分割线------------------------------------------------------
现在分析下AbstractExecutorService的方法
java.util.concurrent.AbstractExecutorService#invokeAny
public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException {
try {
return doInvokeAny(tasks, false, 0);
} catch (TimeoutException cannotHappen) {
assert false;
return null;
}
}
java.util.concurrent.AbstractExecutorService#doInvokeAny
private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
boolean timed, long nanos)
throws InterruptedException, ExecutionException, TimeoutException {
//任务不能为空
if (tasks == null)
throw new NullPointerException();
int ntasks = tasks.size();
//至少要有一个任务
if (ntasks == 0)
throw new IllegalArgumentException();
ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
ExecutorCompletionService<T> ecs =
new ExecutorCompletionService<T>(this);
try {
ExecutionException ee = null;
//若设置超时,则计算超时时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
Iterator<? extends Callable<T>> it = tasks.iterator();
//提交执行第一个任务
futures.add(ecs.submit(it.next()));
--ntasks;
int active = 1;
for (;;) {
//获取执行完毕的任务
Future<T> f = ecs.poll();
if (f == null) {
//还有未执行的任务,则继续提交执行
if (ntasks > 0) {
--ntasks;
futures.add(ecs.submit(it.next()));
++active;
}
//已执行完所有任务,但全部失败,退出循环
else if (active == 0)
break;
//设置超时,则等待指定时间
else if (timed) {
f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
if (f == null)
throw new TimeoutException();
nanos = deadline - System.nanoTime();
}
//等待上一个任务执行完毕
else
f = ecs.take();
}
if (f != null) {
--active;
try {
//尝试获取返回值
return f.get();
} catch (ExecutionException eex) {
ee = eex;
} catch (RuntimeException rex) {
ee = new ExecutionException(rex);
}
}
}
if (ee == null)
ee = new ExecutionException();
throw ee;
} finally {
//取消其他执行中的任务
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
}
看到这,肯定是一头雾水,没关系,先继续往下分析
java.util.concurrent.ExecutorCompletionService#submit
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task);
executor.execute(new QueueingFuture(f));
return f;
}
可见,submit方法,主要做两件事,new QueueingFuture(f),executor.execute(xxx)
先分析 new QueueingFuture
//FutureTask的扩展类,在任务执行完毕后,将执行的任务添加到队列中
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
//futureTask执行完毕后,才会往队列里添加元素
protected void done() { completionQueue.add(task); }
private final Future<V> task;
}
分析execute前,得先简单分析下ExecutorCompletionService
//任务执行器
private final Executor executor;
//阻塞队列,存放QueueingFuture已执行完毕的task
private final BlockingQueue<Future<V>> completionQueue;
//构造方法
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
this.aes = (executor instanceof AbstractExecutorService) ?
(AbstractExecutorService) executor : null;
//初始化队列
this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}
ExecutorCompletionService是如何被创建的呢?往回分析下
在doInvokeAny中,创建了ExecutorCompletionService,而且传入的参数为 this,由此可见,
java.util.concurrent.ExecutorCompletionService#submit方法中,executor.execute(xxx),最终调用的就是AbstractExecutorService的子类(如:ThreadPoolExecutor)的execute(Runnable task)方法。现在我们来梳理下调用链。
分析调用链得出整个执行的关键点,就在于completionQueue,这个队列承上启下,承前启后。现在再回过头来分析doInvokeAny。
从调用链分析可得出executor.execute(xxx)是调用线程池的Worker线程来执行task,因此客户端执行doInvokeAny的线程Thread1和Worker执行FutureTask任务的线程Thread2,是异步的。而且Thread2只有执行完毕后,才会调用done()方法往completionQueue中添加元素,由此可见,虽然已提交并执行了一个任务,但完全有可能因为任务并未执行完毕,而导致ecs.poll()返回null。明白这点,再来分析这些if / else 逻辑,就容易理解了。
假设 tasks.size()=10;
客户端执行 doInvokeAny的线程 Thread1;
Worker执行 FutureTask任务的线程 Thread2;
case A:取到执行完毕的任务为null ;
case B:取到执行完毕的任务不为null;
说明如下:
i)Thread1 从 completionQueue获取执行完毕的task时,Thread2已执行完毕,而且正常返回,那么此时必定走 case B逻辑,通过f.get()获取值,并正常返回。此时
active=0,ntasks=9,futures有1个元素
i)Thread1 从 completionQueue获取执行完毕的task时,Thread2还未执行完毕,此时ecs.poll必定为null,因此走case A逻辑,此时由于tasks>0,因此走case A1逻辑,继续提交并执行任务直到有任务执行完毕。若提交到第3个任务时,Thread2执行完毕了,此时ecs.poll不为空,必定走case B逻辑,并通过f.get()返回。
若Thread2执行task发生了异常,f.get()时,会走异常代码块,因此只是记录异常而不是返回退出。由于第一次ecs.poll操作时,已经取走了元素,因此继续循环判断时,ecs.poll必定为空,此时tasks>0,走case A1逻辑,继续并执行任务。由此可见,若任务执行失败时,会接着继续执行下一个任务,直到成功或者所有任务执行完毕。
i)倘若Thread1将所有任务都提交执行了,Thread2还未执行完毕,此时
active=10,ntasks=0,futures有10个元素
case A1,case A2,case A3都不满足,因此会走case A4逻辑,通过ecs.take,将Thread1阻塞在completionQueue的notEmpty条件队列中,直到有任务执行完毕,往completionQueue中添加元素时,才会将Thread1从条件队列移到同步队列中并唤醒Thread1。
i)若设置了超时,就走case A3逻辑,将Thread1阻塞指定时长,若指定时长后,还未取到元素,只直接抛出异常。
最后再来分析下finally代码块。
finally {
//取消其他执行中的任务
for (int i = 0, size = futures.size(); i < size; i++)
futures.get(i).cancel(true);
}
从上面分析得出,整个过程可能提交了多个任务,当有一个执行完毕时,需要取消futures中的其他任务。具体就是调用FutureTask的cancel方法。
至此,invokeAny方法分析完毕。