ThreadPoolExecutor是常用的线程池类,也是面试的必问题,这里来记录下重点流程。
ThreadPoolExecutor构造方法
ThreadPoolExecutor提供了4个构造方法,它们最终都会调用7个参数的构造方法:
corePoolSize —— 核心线程数
maximumPoolSize —— 最大线程数
keepAliveTime —— 线程存活时间
unit —— keepAliveTime的时间单位,一般为SECOND
workQueue —— 存放Runnable任务的队列(读取元素时如果长度为0,则会阻塞等待)
threadFactory —— 线程工厂,用于生成新的线程
RejectedExecutionHandler —— 拒绝客户端的任务请求时回调。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
来看下AsyncTask中初始化线程池的实际案例:
AsyncTask中实例化ThreadPoolExecutor时传入了6个参数,没有设置rejectHandler。
//AsyncTask.java
//核心线程数为CPU个数-1
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
//最大线程数为CPU2倍+1个
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//线程存活时间为30秒
private static final int KEEP_ALIVE_SECONDS = 30;
//线程工厂类实例
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
//任务队列,长度为128
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
public static final Executor THREAD_POOL_EXECUTOR;
//产生ThreadPoolExecutor实例
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,/*时间单位为秒*/
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
接下来主要分析2个问题:
1. 核心线程池和最大线程池如何实现和如何协作的?
2. workQueue队列如果用于任务的处理?
workQueue它是一个保存Runnable任务的列表,类型是BlockingQueue,用于存放从客户端传入的Runnable对象。
另外,还有一个名为workers
的HashSet,用于保存Worker实例。Worker负责维护一个线程并在该线程中执行来自workQueue的任务。
private final BlockingQueue<Runnable> workQueue;
private final HashSet<Worker> workers = new HashSet<>();
先来了解Worker的作用。
Worker
Worker是一个任务执行体,它从ThreadFactory获得线程之后,在该线程中循环执行从workQueue获得的待执行的任务。
Worker本身实现了Runnable接口,在初始化线程时将自己作为参数,当线程start后就会执行它run方法中的 runWorker
,开始执行任务。
线程池实际上并不是直接通过一个数据结构来管理Thread,而是将Thread交给Worker类管理。
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
private static final long serialVersionUID = 6138294804551838833L;
final Thread thread; //线程
Runnable firstTask; //第一个要被执行的任务
volatile long completedTasks;
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public void run() {
runWorker(this); //执行任务
}
...
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
...
}
线程池状态和worker的数量保存在一个整形之中:
workerCountOf
方法用于计算现存的worker数量,在增加和删除worker时调用compareAndIncrementWorkerCount
或compareAndDecrementWorkerCount
方法来计数。
runStateOf
用于获取线程池状态,当状态不再是RUNNING时,表示线程池进入即将关闭的状态。
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (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;
//获取线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//获取worker数量
private static int workerCountOf(int c) { return c & CAPACITY; }
//计算合并“线程池状态”和“worker数量”到一个整形中
private static int ctlOf(int rs, int wc) { return rs | wc; }
//worker计数+1
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
//worker计数-1
private boolean compareAndDecrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect - 1);
}
了解了worker和前提知识后,现在从入口方法execute
开始梳理下流程。
execute方法
使用ThreadPoolExecutor执行Runnable时,我们会用到它的execute方法,execute处理新任务的逻辑如下:
1. 如果现有线程数少于核心线程数,则直接新开一个线程来处理任务;
2. 如果任务成功被放入workQueue,而之后线程池如果进入SHUTDOWN状态则回退任务并reject,否则新开一条非核心线程处理。
3. 如果任务无法加入到workQueue,新开线程也失败,那么也是reject。(SHUT DOWN状态或者线程池饱和)
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { //情况1.核心线程还没满,创建新的核心线程来执行任务
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) { //情况2.将任务放入队列
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command)) //再次检查线程池的状态,是否在运行中
reject(command); //如果线程池进入停止/关闭等状态,那么回退这次操作并通知客户端的reject handler
else if (workerCountOf(recheck) == 0) //尝试线程池状态正常,启动一个非核心线程来处理任务
addWorker(null, false);
}
else if (!addWorker(command, false)) //情况3:线程池不在工作状态,再尝试用非核心线程来处理任务
reject(command);//失败,通知reject handler
}
addWorker
方法负责创建Worker并新建线程开始执行任务,它接受Runnable和core2个参数,其中core表示是否使用核心线程。
addWorker
返回false的情况3种:1. 线程池被STOP或SHUT DOWN; 2. 线程工厂创建线程失败; 3. worker数量已经超过最大线程数。
如果不存在返回false的情况,那么将会新建一个Worker,并开始在线程中执行任务。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
//检查线程池是否工作状态,和输入的runnable是否为空
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
//worker数量超过核心线程数或者最大线程数,返回false
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//worker计数器+1,并继续执行后面的代码
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
}
}
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())
workers.add(w); //把Worker放入HashSet
int s = workers.size();
if (s > largestPoolSize) //largestPoolSize只是个跟踪线程池数量的变量
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start(); //线程开始运行
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
回看Worker的构造方法,Thread创建时传入的runnable参数就是worker对象,所以线程实际上会执行Worker.run()
方法,即运行runWorker
方法,从workQueue中循环获取runnable对象并执行。
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) { //从workQueue中获取下一个任务
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 {
task.run(); //执行Worker的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
方法)会循环地从workQueue
中获取任务来执行,workQueue
是BlockingQueue
类型的队列,特点是调用poll
或者take
获取下一个数据的时候,如果队列为空则会阻塞等待,其中poll
方法还带timeout参数,可以限定阻塞的最长时间。利用这个特点,就可以控制线程退出或者继续执行。
由于BlockingQueue
是一个接口,我们来看下AsyncTask中具体用到的LinkedBlockingQueue
中的poll
方法和take
方法来理解下。
LinkedBlockingQueue::poll
方法接受timeout和时间单位2个参数,timeout参数的作用在于设置阻塞等待的时间,在调用poll
方法时如果队列为空,那么允许等待新元素的插入。如果在达到超时时间后依然没有新元素,那么直接返回null:
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) { //如果队列为空,会等待长度为timeout的时间,超时会返回null
if (nanos <= 0L)
return null;
nanos = notEmpty.awaitNanos(nanos);
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
而take
方法没有timeout的设置,它是会一直阻塞,直到新元素插入到队列中:
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
while (count.get() == 0) {
notEmpty.await();
}
x = dequeue();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
那么,结合前面分析的runWorker
方法就能理清整个套路了:
1. 带timeout参数的poll
方法,用于非核心线程或者允许timeout的核心线程来获取任务,一旦timeout并返回null后,该线程就结束了;
2. 而take方法被用于不允许timeout的核心线程中,线程会一直保持循环阻塞获取任务,并执行任务。