在实际项目中,如果因为想异步执行暂时性的任务而不断创建线程是很浪费资源的事情(当一个任务执行完后,线程也没用了)。这种情况下,最好是将任务提交给线程池执行。

所谓池,就是将管理某一种资源,对资源进行复用的对象。线程池就是对线程管理的对象。

本文就是介绍线程池内部是如何管理线程,并复用线程的。

相关接口

JDK在内部对线程池提供了大致四层的接口(类)用来提供线程池的行为,分别是顶层接口Executor(这个接口可以理解为执行器,负责执行任务,),可关闭的执行器ExecutorService(这个接口开始才具备线程池的概念),提供线程池基本框架的抽象类AbstractExecutorService,以及线程池的具体实现ThreadPoolExecutor

Executor

/**
* 线程池顶层接口
* 可以执行提交的命令
*/
public interface Executor {
/**
* 执行提交的命令
* @param command
*/
void execute(Runnable command);
}

Executor只定义了线程池一个行为execute()方法,负责执行提交的任务。

ExecutorService


package java.util.concurrent;
import java.util.List;
import java.util.Collection; public interface ExecutorService extends Executor {
/**
* 关闭线程池,已提交的任务会等待直到执行完,但不再接受新提交的任务
*/
void shutdown(); /**
* 立即关闭线程池,尝试停止正在执行的任务
* @return 返回线程池中未开始执行的任务
*/
List<Runnable> shutdownNow(); /**
* 判断线程池是否被关闭
*/
boolean isShutdown(); /**
* 返回线程池是否被终止,只有当线程终端且任务均停止时,才返回true
*/
boolean isTerminated(); /**
* 关闭线程池,并在指定时间内等待任务结束
* @param timeout
* @param unit
* @return
* @throws InterruptedException
*/
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException; /**************execute 和 submit的最大区别是submit可以获得任务的返回值**************/ /**
* 提交一个Callable
* @param task
* @param <T>
* @return
*/
<T> Future<T> submit(Callable<T> task); /**
* 提交一个Runnable,并通过result传递返回值
* @param task
* @param result
* @param <T>
* @return
*/
<T> Future<T> submit(Runnable task, T result); /**
* 提交一个Runnable
* 由于Runnable没有返回值,此时的Future.get()值一定为null
*/
Future<?> submit(Runnable task); /**
* 执行一系列的任务,当这些任务都执行完成时,将返回一个装有任务的返回值的列表
* @param tasks
* @param <T>
* @return
* @throws InterruptedException
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
throws InterruptedException; /**
* 在指定时间内,执行一系列的任务
* @param tasks
* @param timeout
* @param unit
* @param <T>
* @return
* @throws InterruptedException
*/
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException; /**
* 执行一系列的任务,当其中一个执行完成时,就返回该任务的结果
* @param tasks
* @param <T>
* @return
* @throws InterruptedException
* @throws ExecutionException
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
throws InterruptedException, ExecutionException; /**
* 在指定时间内执行一系列任务,当其中一个任务执行完成时,就返回该任务的结果,如果规定时间内没有任务执行完,则返回TimeoutException
* @param tasks
* @param timeout
* @param unit
* @param <T>
* @return
* @throws InterruptedException
* @throws ExecutionException
* @throws TimeoutException
*/
<T> T invokeAny(Collection<? extends Callable<T>> tasks,
long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}

线程池基本的行为都是在ExectuorService中定义的(Executors线程池工具类返回的线程池对象也是基于这个接口),因此我们可以把该接口真正定义为线程池。

线程池基本行为:

任务执行的相关行为:通过submit()接受任务的提交,并返回Future类提供返回计算结果的方式。submit()接受的任务类型可以是Callable,Runnable

同时提交多个任务的行为:这类行为又可以分为两类,1)等待(可指定等待时间)所有任务执行的方法invokeAll;2)只需一个任务完成(可指定等待时间)就可以返回的方法invokerAny

线程池关闭的行为:主要是shutdownterminate相关方法。

AbstractExecutorService

AbstractExecutorService抽象类是对ExecutorService的抽象实现。JDK中 Abstract打头的类通常是对某类接口定义了基本的行为(模版模式)。AbstractExecutorService也是如此。它提供的定义的行为主要分为三类:

  • 封装任务的能力

    线程池内部能接受的任务单位为FutureTask,对于外部向线程池提交的RunnableCallable的任务,线程池会通过newTaskFor将任务封装成FutureTask
        //将Runnable包装成RunnableFuture(这里用的是其实现类FutureTask),因为线程池内部接收的运行单位均是Runnable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
} //将Callable包装成RunnableFuture
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}

具体如何封装我们在FutureTask中介绍。

  • submit行为主体框架

    submit主要就是接受提交的任务,然后通过newTaskFor将其封装成FutureTask,通过execute()执行任务。execute是线程池任务执行的主要逻辑,AbstractExecutorService并没有提供相关实现,在之后介绍ThreadPoolExecutor时会着重介绍。
  • invokeAll和invokeAny的实现

    关于invokeAll和invokeAny的方法由于篇幅限制,本文不再做额外介绍。读者可以看我对AbstractExecutorService源码注释

ThreadPoolExecutor

ThreadPoolExecutor是线程池的一种实现(ScheduledThreadPoolExecutor是另一种实现,在线程池的功能之外,还提供了定时执行任务的能力)。

FutureTask

FutureTask是线程池任务执行的单位,实现了RunnableFuture接口(既是Runnable又是Future,说明是一个可执行的任务,又可以通过Future的特性获取计算结果)。

内部变量

//任务的运行状态
private volatile int state;
private static final int NEW = 0;//初始状态
private static final int COMPLETING = 1;//完成中(输出值未被设置)
private static final int NORMAL = 2;//正常完成
private static final int EXCEPTIONAL = 3;//异常
private static final int CANCELLED = 4;//取消
private static final int INTERRUPTING = 5;//中断中
private static final int INTERRUPTED = 6;//已中断 //内部实际的任务
private Callable<V> callable;
//返回结果
private Object outcome; // non-volatile, protected by state reads/writes
//任务执行的线程
private volatile Thread runner;
//等待结点(头节点)
private volatile WaitNode waiters;

其中state表示任务状态,一共定义了7种状态。其中比较特殊也比较重要的一个状态是COMPLETING。表示完成中,是一种中间状态,只任务已经计算完成,但还没设置outcome

callable是外部提交的任务。上文介绍过FutureTask会包装传入的CallableRunnable,正是通过这个字段来存储实际的任务。另外对于Runnable而言,是通过一个适配器RunnableAdapter转成了Callable(适配器模式的应用)。

outcome用来存放计算任务的结果值。

runner表示执行任务的线程。

waiters是一个比较重要的概念,它会将在为获取计算结果而进入阻塞的线程形成一个链表。用于得到计算结果后的唤醒。

主要方法

对于FutureTask而言,因为其具有RunnableFuture的双重身份,因此最主要的方法也就是任务的执行方法run()和结果的获取方法get()

执行任务run()
    //任务的执行方法
public void run() {
//任务已经被执行 或是 任务已经绑定过执行的线程 直接返回
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//任务待执行 => 执行任务
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//真正执行提交的任务
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
//任务执行完毕 设置结果,此时会唤醒等待的节点
if (ran)
set(result);
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}

run()方法主要可以分为三个过程:

  1. 校验任务状态:看任务是否被已经被执行过
  2. 直接调用Callable.run(),执行外部提交的任务
  3. 如果成功执行,则调用set设置结果
获取结果get()
public V get() throws InterruptedException, ExecutionException {
int s = state;
//如果任务状态还没完成,阻塞等待任务完成
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}

如果任务已经执行完,则返回任务结果。如果任务还没完成,则通过awaitDone()方法让线程挂起。

    /**
* 等待任务完成
* @param timed
* @param nanos
* @return
* @throws InterruptedException
*/
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
//计算超时时间
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
//自循环等待
for (;;) {
//如果线程已经被中断,则退出等待
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
} int s = state;
//如果任务已完成
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet //任务正在设置结果(此时不会超时,等待输出结果)
Thread.yield();
else if (q == null)//进入这里说明任务还没完成,因此创建等待的节点
q = new WaitNode();
else if (!queued)//将创建的节点通过CAS放入队列(作为链表头节点)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) { //让队列中的节点被挂起
nanos = deadline - System.nanoTime();
//已经超时
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
//挂起等待指定时间
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this); //挂起等待
}
}

awaitDone()是通过自循环加上LockSupport.park()的方式让线程挂起等待的,对于新加入等待的线程而言,第一次循环会创建出waitNode对象,第二次循环尝试CAS更新waitNode对象为waiters(等待线程节点链表)的头节点,之后在让线程通过LockSupport.park()挂起线程。如果等到时间结束或是被唤醒,线程会再次进入循环,重新判断是否中断唤醒(抛出异常),还是任务完成唤醒(返回状态)。

ThreadPoolExectuor

在了解ThreadPoolExecutor工作流程前,先看看ThreadPoolExecutor内部有哪些变量。

内部主要变量

核心参数

线程池在创建时,可接受一系列参数用于定制线程池。这些参数如下:

corePoolSize:池核心线程数,当池中线程数小于核心线程数时,线程池接受新任务会优先创建新Worker(即新线程),直接分配该任务给Worker。而当池中线程数大于等于corePoolSize时,任务则会先添加进workQueue,直到workQueue已满,线程池才会再创建worker执行任务。关于这一流程,我们在文章最后放一个测试代码,验证下这个流程。

maximumPoolSize:池最大线程数,当池中线程数大于maximumPoolSize时,无法在创建新线程。

keepAliveTIme:最大空闲活跃时间。通常当池中线程数小于等于corePoolSize时,keepAliveTime不会生效(也可以通过allowCoreThreadTimeOut()方法设置使其对小于corePoolSize时也生效),而当线程数大于corePoolSize时,如果有worker空闲超过keepAliveTime时,该worker会退出。

workQueue:任务队列,除了直接提交给Worker外的任务都将放入workQueue中,当Worker空闲时,会从队列中获取新的任务执行。当workQueue已满,且线程池无法创建新的Worker时,再提交的任务会被池的拒绝策略拒绝。

threadFactory:线程工厂,用于定制线程池创建的线程。

表示runState和workerCount的变量ctl

Doug Lea老爷子用一个原子整数ctl同时表示线程池的runState(线程池状态)和workerCount(线程池线程数)两个状态。

具体做法是32位的整形被一拆为二,高3位用来表示runState,低29位用来表示workerCount(因此线程池目前理论支持的最大线程数为2^29-1)。

其中对于runState,线程池定义了五种状态。这五种状态按值从小到大排分别是RUNNING,SHUTDOWN,STOP,TIDYINGTERMINATED

工人的集合workers

wokers是一个存放Worker的HashSet。当前线程池中存活的线程都会存在workers中。

并发控制相关的变量

mainLock是线程池的主锁。用来在并罚下控制某些操作。

存储统计信息的变量

largestPoolSize用来记录线程池历史最大线程数。

completedTaskCount用来统计线程池已完成任务数。

任务的执行者Worker

Worker可以直译为工人,是线程池内部任务执行的主体。


/**
* Doug Lea将线程池中的线程封装成Worker
* Worker即是一个Runnable 又是一个AQS(且为不可重入的AQS)
*/
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{ private static final long serialVersionUID = 6138294804551838833L; //执行上执行任务的线程
final Thread thread; //第一个任务
Runnable firstTask; //用于统计单个线程完成的任务数
volatile long completedTasks; /**
* 创建Worker
* @param firstTask
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
//分配Task,并创建执行任务的线程
this.firstTask = firstTask;
//注意线程传入的runnable 就是Worker,因此线程只会绑定Woker的run方法,
// 提交的线程池的任务并非直接被线程执行,而是由worker取出workQueue中的task,然后调用task.run
this.thread = getThreadFactory().newThread(this);
} /**
* Worker的run方法
* 工人的使命就是去执行任务,因此runWorker()主要任务就是取任务并执行
*/
public void run() {
runWorker(this);
} // Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state. 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) {
}
}
}
}

Worker其实就是对线程的封装,同时还继承了AQS,从尝试上锁的方式中我们可以发现Worker是不可重入的锁。

另外,Worker在创建时可以直接被分配任务(可以让提交至线程池中的任务不先放至workQueue中)。

Worker还通过completedTasks统计该线程执行完的任务数。

执行任务execute

线程池的任务执行先通过execute()方法提交任务。被提交的任务通常有三种情形:1)创建新的Worker直接执行任务,2)将任务加入workQueue,等待其他worker执行,3)任务被拒绝。

    /**
* 线程池执行任务主方法
* @param command
*/
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.
*/
int c = ctl.get();
//如果线程数 小于 核心池数
if (workerCountOf(c) < corePoolSize) {
//创建新Worker:只有创建worker,并成功运行后
if (addWorker(command, true))
return;
c = ctl.get();
}
//代码执行到此处,说明可能线程数已经大于等于核心线程数 或是 addWorker失败了
//如果线程池仍为RUNNING 并添加任务至任务队列成功
if (isRunning(c) && workQueue.offer(command)) { //FIXME 里面的逻辑对应的情形还没想明白
//double check
int recheck = ctl.get();
//再次检查时如果发现线程池状态不在运行且移除任务成功,则对任务做拒绝处理
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0) //如果此时workerCount为0,则添加Worker(避免任务在队列里,但是没有worker去取的情况)
addWorker(null, false);
}
else if (!addWorker(command, false)) //说明线程池不在运行状态或是任务添加进队列失败 且再次尝试添加Worker后又失败
reject(command); //说明线程池不在运行了 或 任务队列已经满了 或WorkerCount已经最大了, =>无法添加任务,对任务做拒绝处理
}

其中是否能添加Worker是有addWorker方法决定。决定的因素主要是池的状态,池的线程数和workQueue的长度。

private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
//获取线程池的runState
int c = ctl.get();
int rs = runStateOf(c); // Check if queue empty only if necessary. // 如果线程池的状态为关闭(>= SHUTDOWN),则不允许增加线程
// 但是有一种情况例外,就是线程池状态为SHUTDOWN 但是此时添加Worker不是因为插入新任务,而是希望执行之前队列中的任务(因为SHUTDOWN时线程池不能接受新任务,但是要执行完旧的任务)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
//
for (;;) {
//获取线程数
int wc = workerCountOf(c);
//如果线程池已经大于最大线程数 或者 (线程池 大于核心线程数 或是最大线程数(依据core标志))
//则无法创建worker
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//CAS更新线程数,如果失败,继续循环尝试
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
// 如果线程池状态发生了变更(通常是线程池被关闭),则需要继续循环(重新回到第一个if条件那退出)
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
} //执行到这里说明需要增加Worker
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//创建新Worker
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//上锁,因此添加worker时是线程安全的
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get()); //判断线程池依旧在运行状态 或是SHUTDOWN状态但非提交新任务
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//检查Worker的线程必须是新创建的,(未被启动)
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//添加Worker
workers.add(w);
int s = workers.size();
//更新历史最大池容量
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//添加成功,则启动线程,Worker开始干活
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//Worker启动失败,则清理一些数据
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}

当任务成功提交给线程池执行后,任务会由Worker线程异步执行。

workerrun()方法中(其实是调用了线程池的runWorker()方法),worker会从线程池中取出任务,并执行任务的run方法。

runWorker()方法如下:

    /**
* Worker执行任务的逻辑,(Worker不断从workQueue里取出任务并执行的过程)
* @param w
*/
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
//获取第一个任务,在添加Worker时可能为该Worker设置初始任务(不需要再从workQueue中获取),也可能未设置初始任务而从workQueue中获取
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//worker不断获取任务
//这里需要注意一点,在getTask()时,线程因为阻塞而被Park(),会因为interrupt()方法唤醒,而抛出异常,让getTask()提前结束一次循环
//从而可能在下一轮中返回null(这就是控制线程中断位实现Worker退出的机制)
while (task != null || (task = getTask()) != null) {
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 // wt线程未被中断 但是线程池被STOP 则要标记worker线程的中断状态
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 因为此时的工作线程就是worker thread)
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);
}
}

runWorker()主要的过程就是:

1.取出task(可能会引起阻塞)

2.调用任务的run方法执行任务

3.更新完成任务数

如果worker在getTask()超时或返回null,worker会退出被线程池回收。

上述代码中task.run()方法就是调用FutureTask.run()方法。FutureTask又是对原始任务的封装。其会在任务执行时调用原始任务的run方法。并在执行完成后,设置任务输出结果,并通知等待计算结果的线程。

线程池中的RUN方法可以说一层套一层。首先,最外层的是Worker的run方法。Worker作为工人,必须是需要工作的,因此它的run方法就是工人在工作:获取任务,并完成任务。而工人获取到的任务就是FutureTask,是线程池封装过的工作内容。因为线程池需要在任务完成时输出结果。而FutureTask内部的Callable才是真正的原始任务。这里可能稍微有点绕,读者需要好好理解下。

对于上述线程池任务执行的过程,我整理了一份流程图帮助大家理解:

关闭线程池

在我们不需要线程池时,可以通过关闭线程池的方式释放线程池的资源。关闭线程池可以分为优雅关闭shutdown和强制关闭shutdownNow两种

优雅关闭SHUTDOWN

当我们调用shutdown()方法时,线程池会由RUNNING状态进入到SHUTDOWN状态。此时线程池将不再接受新任务的提交,随着池中任务不断被执行完,池中的线程也不断被回收。当池中不再有线程时,线程将转入TIDYING状态。表示资源已经清理干净,最后在进入TERMINATED状态。线程池终止。

    /**
* 关闭线程池
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//校验检查状态
checkShutdownAccess();
//设置线程池rs
advanceRunState(SHUTDOWN);
//中断空闲的线程(会从getTask()中返回null,并退出runWorker()方法)
interruptIdleWorkers();
//钩子函数
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
//调用tryTerminate让线程池从SHUTDOWN状态逐步清理为TERMINATED状态
tryTerminate();
}

关闭过程如下:

第一步是出于安全性的考虑,先进行权限的校验。

第二步时更新线程池状态为SHUTDOWN,该状态会控制线程池不再接受新任务。

第三步是中断空闲的线程interruptIdleWorkers()

第四步通过onShutdown()方法给子类留下钩子函数。

第五步调用tryTerminate()方法尝试将线程池终止。

其中interruptIdleWorkers()方法是中断非工作的线程,具体执行方法在interruptIdleWorkers(boolean onlyOne)中。

    /**
* 中断空闲的Worker
* @param onlyOne
*/
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//worker线程未被中断 且 tryLock成功(说明worker此时为空闲)
//因为worker在执行任务前会上锁,且worker作为AQS,是一个不可重入的锁(tryAcquire只允许从0更新1)
//因此tryLock成功,说明worker未在执行任务
if (!t.isInterrupted() && w.tryLock()) {
try {
//设置中断标记
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}

对于上述这个方法有两点需要思考:

1)线程池是如何判断空闲线程的?

2)线程被标记中断后,会发生什么?

先回答第一个问题,我们已经知道Worker是一个不可重入的锁,而且Worker在执行任务前会先通过lock()方式对自己进行加锁,在任务完成后在通过unlock()释放锁。因此上述方法中的tryLock()如果返回true。说明此时线程是空闲的。

在回答第二个问题,回顾一个额外的知识点,线程因为LockSupport.park()进入WAITING或是TIMED_WAITING状态后,可以通过 interrupt()方法中断醒来。 那么池中的线程什么时候会被挂起?代码中大概是有两处方法,一是上面介绍的线程在执行任务前,会通过worker.lock()方先获取锁,此时如果获取不到锁就会进入阻塞。另一处则是getTask()时,worker等待任务的获取。

分析第一处的情形,因为上锁调用的lock()方法而非lockInterruptibly()(前者会在中断醒来后继续尝试获取锁,而后者直接抛出异常退出锁的获取)。

在分析第二处情形,阻塞队列无论在poll()还是take()时,等待的线程如果发生中断,则都会抛出异常,而异常会在getTask()中被catch捕获而重新进入for循环中,此时线程池的runState已经被更新为SHUTDOWN,因此线程会从getTask()中退出,且返回值为null。进而退出了runWoker()方法,标志着Worker完成了工作使命,将通过processWorkerExit()方法释放资源。(读者可以回过头去看看上文的runWoker()方法)。

对于关闭中的第四步tryTerminate()方法,其实就是在线程逐步清理完资源后,将线程池的状态更新为TERMINATED

   final void tryTerminate() {
for (;;) {
int c = ctl.get();
//以下三种情况不能将线程池状态改为TERMINATED
//1.线程池仍在运行
//2.线程池已经为TIDYING或是TERMINATED(不需要在转)
//3.线程池状态为SHUTDOWN 但是 workQueue还不为空
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return; //其他情况都需要进行TERMINATED的情况 //如果workerCount不为0
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
} //说明此时workerCount 为0
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//CAS操作成功,线程池状态被更新为TIDYING
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
//钩子函数
terminated();
} finally {
//更新线程池状态为TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}

tryTerminate()方法只是尝试将线程池的状态逐步更改为TERMINATED,并不能保证此次调用一定成功,但是因为每当有一个Worker退出,都会调用此方法,线程池终将会被更新为TERMINATED

强制关闭SHUTDOWNNOW

强制关闭shutdownNom()方法会直接将线程池的状态设置为STOP,然后中断线程,并返回未执行的任务。

具体的代码不再展开。

ThreadPoolExecutor的拒绝策略

当线程池被关闭或是线程池队列已满且线程数已经最大,新添加的线程将会被拒绝。对于拒绝后的任务如何处理将有拒绝策略决定。

线程池提供了四种拒绝策略的实现。默认的策略是抛出异常。

CallerRunPolicy 任务提交方自己执行

    /**
* 拒绝策略1:由用户执行
*/
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { } /**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}

AbortPolicy 默认策略(抛出异常)

    /**
* 拒绝策略2:抛出异常(默认策略)
*/
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { } /**
* Always throws 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());
}
}

DiscardPolicy 丢弃任务

    /**
* 拒绝策略3:拒绝该任务
*/
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { } /**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}

DiscardOldestPolicy 丢弃最古老的任务

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { } /**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}

线程池创建线程过程测试程序

线程池中有一个高频面试点。就是线程池在不断接受任务时,分配线程的过程。

我们通过以下代码测试下:

public class ThreadPoolExecutorTest {

    public static void main(String[] args) throws InterruptedException {
BlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(5);
ExecutorService es = new ThreadPoolExecutor(2, 10, 10000, TimeUnit.MILLISECONDS, blockingQueue, new MyThreadFactory()); Thread.sleep(1500); for(int i=1; i<=10; i++){
final int idx = i;
es.submit(() ->{
System.out.println("- Execute Task " + idx + "");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread.sleep(200);
} } static class MyThreadFactory implements ThreadFactory{
private int i = 0; @Override
public Thread newThread(Runnable r) {
System.out.println("+ Create Thread Index:" + i++);
Thread t = new Thread(r);
t.setDaemon(false);
return t;
}
}
}

程序的运行结果是:

+ Create Thread Index:0
- Execute Task 1
+ Create Thread Index:1
- Execute Task 2
+ Create Thread Index:2
- Execute Task 8
+ Create Thread Index:3
- Execute Task 9
+ Create Thread Index:4
- Execute Task 10
- Execute Task 3
- Execute Task 4
- Execute Task 5
- Execute Task 6
- Execute Task 7

可以看到在核心线程数未满时,新提交的任务将直接由新线程执行,而当核心线程已满,线程会优先提交到workQueue,等待已有的线程去执行。直到workQueue也满了,线程池才会再去创建新线程。如果线程数也大于maximumPoolSize时,再提交的任务会被拒绝。

总结

个人觉得线程池的代码难度相较于AQS要简单一些。但是概念比较多,比如FutureTaskWorker等,run方法一层嵌套一层。但是主体任务执行流程还是比较清晰的,执行过程以execute()为入口,关闭过程以shutdown()为入口。

本文并未涉及全部的源码分析,如果想了解更多的读者,可以查看我的源码阅读项目read-jdk

谈谈Java的线程池设计的更多相关文章

  1. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  2. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

  3. 深入理解Java之线程池

    原作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...

  4. 深入理解Java之线程池(爱奇艺面试)

    爱奇艺的面试官问 (1) 线程池是如何关闭的 (2) 如何确定线程池的数量 一.线程池销毁,停止线程池 ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown() ...

  5. [转]深入理解Java之线程池

    原文链接 原文出处: 海 子 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这 ...

  6. 沉淀再出发:java中线程池解析

    沉淀再出发:java中线程池解析 一.前言 在多线程执行的环境之中,如果线程执行的时间短但是启动的线程又非常多,线程运转的时间基本上浪费在了创建和销毁上面,因此有没有一种方式能够让一个线程执行完自己的 ...

  7. Java并发--线程池的使用

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  8. Java之线程池(一)

    在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统 ...

  9. Java:线程池

    Java:线程池 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 获取多线程的方法: 实现 Runnable 接口 实现 Callable 接口 实例化 Thre ...

随机推荐

  1. ASP.NET CORE WEBAPI文件下载

    ASP.NET CORE WEBAPI文件下载 最近要使用ASP.NET CORE WEBAPI用来下载文件,使用的.NET CORE 3.1.考虑如下场景: 文件是程序生成的. 文件应该能兼容各种格 ...

  2. flask-宏

    flask-宏 模板中的宏跟python中的函数类似,可以传递参数,但是不能有返回值,可以将一些经常用到的代码片段放到宏中,然后把一些不固定的值抽取出来当成一个变量,使用宏的时候,参数可以为默认值. ...

  3. 单线程IP扫描解析

    扫描代码: private void Button_Click(object sender, RoutedEventArgs e) { a5.Items.Clear(); string str = t ...

  4. 34.3 转换流 InputStreamReader OutStreamReader

    转换流: 把字节输出流转换成字符输出流 标准输入输出流:传输的对象是字节流 System.in . System.out 标准输入输出流 public static final InputStream ...

  5. spark error Caused by: java.io.NotSerializableException: org.apache.hadoop.hdfs.DistributedFileSystem

    序列化问题多事rdd遍历过程中使用了没有序列化的对象. 1.将未序列化的变量定义到rdd遍历内部.如定义入数据库连接池. 2.常量定义里包含了未序列化对象 ,提出去吧 如下常量要放到main里,不能放 ...

  6. 16-jmeter-CLI模式(无图形界面)

    GUI和非GUI图形界面的使用区别: 非GUI界面:命令模式运行可以将实时的log文件保存到本地,位置可以自定义,不会占用太多资源,可以长时间运行. GUI图形界面:在运行时会消耗资源,且图形界面运行 ...

  7. VM卸载不完全,重装的一个下午

    玩软件就是随时面临着重新来过的危险.今天一不小心就把VM给高爆了,爆的很高的那种. 卸载不完全的VM如何在不重装系统的情况下安装. 首先第一步,肯定是通过控制面板去卸载VM,但是....但是...我靠 ...

  8. 字典树&&AC自动机---看完大概应该懂了吧。。。。

    目录 字典树 AC自动机 字典树 又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种.典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计 ...

  9. char类型及ASCII码之间比较

    在JAVA中,char类型可以直接运算,char在ASCII等字符编码表中有对应的数值对char类型字符运行时,直接当做ASCII表对应的整数来对待 参考 https://blog.csdn.net/ ...

  10. AJ学IOS 之小知识之xcode6自动提示图片插件 KSImageNamed的安装

    AJ分享,必须精品 一:首先看效果 KSImageNamed是让XCode能预览项目中图片的插件 很牛逼,据说写这个插件的牛人在日本~ 主要针对imageNamed:方法 效果如图: 安装: 首先需要 ...