首先我们来看一下ThreadPoolExecutor类的构造函数,看看它需要哪些参数,通过对这些参数的讲解,我们再来分析它的原理以及是如何实现的。这些参数不是每个都需要传入,但是需要理解其中的含义。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory and rejected execution handler.
* It may be more convenient to use one of the {@link Executors} factory
* methods instead of this general purpose constructor.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @throws IllegalArgumentException if one of the following holds:
* {@code corePoolSize < 0}
* {@code keepAliveTime < 0}
* {@code maximumPoolSize <= 0}
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
//构造函数代码代码省略
}
ctl又存储了worker数量也存储了线程池的状态。 从
Running-> ShutDown -> Stop-> TiDying->TERMINATE
* RUNNING: Accept new tasks and process queued tasks
* SHUTDOWN: Don't accept new tasks, but process queued tasks
* STOP: Don't accept new tasks, don't process queued tasks,
* and interrupt in-progress tasks
* TIDYING: All tasks have terminated, workerCount is zero,
* the thread transitioning to state TIDYING
* will run the terminated() hook method
* TERMINATED: terminated() has completed
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
首先当小于corePoolSize时,addWorker,如果可以往队列里还可以塞的话,就塞进去,塞不进去就新增worker,失败就拒绝。
所以这里有2个问题:
1.新增的worker如何执行线程
2.worker如何取队列里面拿线程执行
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
int c = ctl.get();
// 小于corePoolSize直接新增worker
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
// 否则offer塞入队列
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 新增worker后如果不是运行状态则拒绝
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
// 继续新增worker,新增失败则拒绝
else if (!addWorker(command, false))
reject(command);
}
上面代码解释了corePoolSize的原理,它影响到addWorker的调用。带着问题1
我们来看一下addWorker方法做了什么。
try {
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());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
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 {
if (! workerStarted)
addWorkerFailed(w);
}
。将Runnable封装到一个Worker里,然后再封装到一个Thread对象里,然后执行t.start()启动就执行了Worker.run()方法。这里解决了第一个问题,那么第二个问题呢?我们再看看Worker的代码。
Worker的执行
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//一旦一个worker启动以后,就不停的调用getTask()来从队列中拿任务执行
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
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();
} 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);
}
}
这里worker实现了Runnable接口,它本身也是一个可以执行的任务,封装了thread,同时调用runWorker方法执行任务。并且当任务执行完成后循环从队列中获取任务。这里解释了第二个问题。
众所周知,我们可以执行submit方法来向线程池提交任务,然后通过返回的Future对象来获得任务执行的状态,通过future.get()方法来阻塞等待其完成任务。那么这个流程是怎么实现的呢。这里有几个问题:
首先我们来看一下submit方法,它是AbstractExecutorService类的一个方法。它将我们传入的task对象封装到了一个RunnableFuture对象中,实际是一个FutureTask对象,它实现了Future和Funnable接口。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
sys
所以如上图所示,我们的任务被封装在future对象中并交给了线程池,然后被封装到Worker中进行执行。通过对象组合的方式,为我们的runnable增加了很多功能。下面我们来分析一下FutureTask的原理。
Future的状态
public void run() {
if (state != NEW ||
// 把当前线程设置到runner字段中,注意runner字段用volatile修饰了,如果任务已经启动了,则直接返回。防止多个线程同时启动冲突问题。
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
//为什么这里要先获取c再执行c.call()而不是直接用对象的属性callable来执行?是不是和线程安全相关,防止在判断了callable!=null之后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)
//设置结果值,并改变future的状态,同时唤醒等待的线程
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);
}
}
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
//这里是释放阻塞线程的核心方法,释放等待节点WaitNode
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}
我们总结一下,FutureTask的执行是先执行run()方法,执行了其封装的callable对象,即我们要执行的任务,然后将任务执行结果设置到result中,同时唤醒等待线程。在了解如何唤醒等待线程之前,我们先看一下线程是如何阻塞的。
当我们调用future.get时,主要逻辑在awaitDone方法中。
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) {
// 如果大于处理中,那么可能已经处理完成或者处理失败,这里会返回线程状态交给report方法
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)
// 初始化后如果没有加入队列,则加入队列。将新创建的节点的next指向原有的节点,并将waiters属性设置为新的节点。q.next = waiters的表达式的值为waiters。赋值的同时返回被赋的值。
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);
}
}
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
所以从以上代码我们可以看出线程等待时会创建一个节点,节点中记录了调用get()方法的当前线程,并将节点入队。入队后通过LockSupport.park阻塞线程。
知道了如何阻塞线程的,那么再来看唤醒线程就很清晰了。
//这里是释放阻塞线程的核心方法,释放等待节点WaitNode
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
//这里用循环和cas操作来保证同时只有一个线程可以成功执行if语句中的操作。
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
// 将 waiters设置为null,代表已经有线程在处理唤醒了,同时帮助gc
for (;;) {
//循环唤醒所有等待节点中的线程
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
// 这里设置为null还不是很理解,是为了帮助gc吧。降低对环境的影响。
callable = null; // to reduce footprint
}
从上面的代码我们可以看到,future的阻塞和唤醒,是通过FutureTask中的WaitNode链表来实现的。future.get时入队挂起,任务完成时遍历队列唤醒。