Java 中的线程池是并发框架当中运行场景最多的并发工具类,基本上需要异步或者并发执行的任务都可以使用线程池。相比开发人员直接使用手动创建线程,使用线程池可以有以下几个好处。
我们知道了使用线程池的好处,下面我们来看一下使用线程池的典型场景:
下面就写一个简单的 demo 来演示线程池是如何使用的。
public class RunnableDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executorService.execute(new Task());
}
executorService.shutdown();
}
private static class Task implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
}
Executors.newFixedThreadPool(10)
创建工作线程数为 10 的线程池executorService.execute(runnable)
,提交 100 线程,打印当前线程名executorService.shutdown()
,关闭线程池Executor
:一个执行提交的 Runnable
任务的对象。这接口提供了一种方法来解耦任务提交每个任务如何运行的机制,包括线程的细节使用、日程安排等通常使用 Executor
而不是显式地创建线程。例如,而不是为每个线程调用new Thread(new(RunnableTask())).start()
。ExecutorService
:ExecutorService 可以被关闭,这将导致它拒绝新任务。提供了两种不同的方法关闭 ExecutorService
。shutdown()
方法将允许以前提交的任务在以前执行终止,shutdownNow
方法阻止等待从开始的任务和尝试停止当前正在执行的任务。在终止时,执行人没有正在执行的任务等待执行的任务,不能提交新的任务。一个未使用的ExecutorService
应该关闭以允许回收资源。AbstractExecutorService
:提供了 ExecutorService 接口的默认实现执行方法。这个类实现了 submit()
、invokeAny()
和 invokeAll()
方法使用由 newTaskFor()
返回的 RunnableFuture
,默认值为到这个包中提供的 FutureTask
类。例如, submit(Runnable)
的实现创建了一个关联被执行的 RunnableFuture
和返回RunnableFuture
实现,而不是 FutureTask
。子类可以重写 newTaskFor
方法。ThreadPoolExecutor
:线程池解决两个不同的问题:它们通常在执行大量时提供改进的性能异步任务,由于减少了每个任务的调用开销,它们提供了一种限制和管理资源的方法,包括执行任务集合时使用的线程。ThreadPoolExecutor
也维护一些基本的统计信息,例如已完成任务的数量。我们可以通过 ThreadPoolExecutor 来创建一个线程池。
new ThreadPoolExecutro(corePoolSizie, maximumPoolSize, keepAliveTime, milliseconds, runnableTaskQueue, handler);
创建一个线程池需要输入几个参数,如下:
corePooloSize(线程池基本大小)
:当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的 prestartAllCoreThreads() 方法,线程池会提交创建并启动所有基本线程。
runnableTaskQueue(任务队列)
:用于保存等待执行的任务的阻塞队列。可以选择以下几人阻塞队列。
ArrayBlockingQueue
:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。LindedBlockingQueue
: 是一个基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue
。静态工厂方法 Executors.newFixedThreadPoll()
使用了这个队列。SynchronousQueue
: 一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue
,静态工厂方法 Executors.newCachedThreadPool
使用了这个队列。PriorityBlockingQueue
:一个具有优先级的无限阻塞队列maximumPoolSize(线程池最大数量)
:线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果。
ThreadFactory
: 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字。使用开源框架 guava 提供的 ThreadFactoryBuilder 可以快速给线程池里的线程设置有意义的名字,代码如下。
new ThreadFactoryBuilder().newNameFormat("XXX-task-%d").build();
RejectedExecutionHandler
(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy, 表示无法处理新任务时抛出异常。线程池默认有以下 4 种策略。AbortPolicy
: 这种策略会直接抛出一个 RejectedExecutionException
异常。DiscardPolicy
: 这种策略是丢弃线程,当线程池处理能力不足时,就会把新添加的线程默默的丢弃掉。不会得到通知,使用者也不会知道线程被丢弃掉了。DiscardOldestPolicy
: 这种策略是丢失最老的,当线程池处理能力不足时,它会把最老的存在时间最久的任务丢弃掉。以便来提交刚刚提交的任务。这种策略比 DiscardPolicy 策略人性化一点。CallerRunsPolicy
: 这种策略谁提交的这个任务谁就去执行这个任务,这种策略有以下两点好处:一个是和之前的策略不一样,它不会丢弃掉任务,不会业务损失;另一个是它可以让提交的速度降低下来,因为主线程一直提交任务,线程池能力不足够的时候就会让主线程执行任务。执行任务是需要时间的,所以提交速度会降低下来。当然,也可以根据应用场景需要来实现 RejectedExecutionHandler
接口自定义策略。如记录日志或持久化存储不能处理的任务。
keepAliveTime(线程活动保持时间)
: 线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
TimeUnit(线程活动保持时间的单位)
: 可选的单位有天(DAYS)、小时(HOURS)、分钟(MINTUES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒).
线程池通常使用工厂类 Executors
来创建,Executors
可以创建 4 种类型的线程池:SingleThreadExecutor
、FixedThreadPool
、CachedThreadPool
和 ScheduledThreadPoolExecutor
。
FixedThreadPool
:创建使用固定线程数的线程池 适用用为了满足资源管理的需求,而需要限制当前线程数量的应用场景,它适用于负载比较重的服务器。ExecutorService newFixedThreadPool(int nThreads);
ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory);
SingleThreadExecutor
:创建使用单个线程的的线程池。适用于需要保证顺序地执行的各个任务;并且在任意时间点,不会有多个线程是活动的场景。ExecutorService newSingleThreadExecutor()
ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory)
CachedThreadPool
。:创建一个会根据需要创建新线程的线程池。它是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器。ExecutorService newCachedThreadPool();
ExecutorService newCachedThreadPool(ThreadFactory threadFactory);
ScheduledThreadPoolExecutor
:创建一个周期执行任务的线程池,通常使用工厂类 Executor 来创建。可以创建一个线程或者若干个线程的 ScheduledThread。ScheduledExecutorService newScheduledThreadPool(int corePoolSize);
ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory);
ScheduledThreadPoolExecutor
适用于需要多个后台线程执行周期任务,同时为了满足资源管理的需求而需要限制后台线程的数据的应用场景。下面是 Executor 提供的创建单个线程的 SingleThreadScheduledExecutor
的 API
ScheduledExecutorService newSingleThreadScheduledExecutor();
ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory);
SingleThreadScheduledExecutor
适用用需要单个后台线程执行周期任务,同时需要保证顺序执行各个任务的应用场景
下面是这几个线程池几个比较重要参数的对比:
Parameter | FixedThreadPool | CachedThreadPool | SingleThreadPool | ScheduledThreadPool |
---|---|---|---|---|
corePoolSize | constructor-arg | 0 | Constructor-arg | 1 |
maxPoolSize | same as corePoolSize | Intege.MAX_VALUE | Intege.MAX_VALUE | 1 |
keepAliveTime | 0 seconds | 60 seconds | 0 | 0 seconds |
下面我们来分析一下线程池内部执行原理。
以下是线程池里面的核心变量。
ThreadPoolExecutor.java
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;
// Packing and unpacking ctl
private static int runStateOf(int c) { return c & ~CAPACITY; }
private static int workerCountOf(int c) { return c & CAPACITY; }
private static int ctlOf(int rs, int wc) { return rs | wc; }
其中 AtomicInteger
类型的变量 ctl
的功能非常强大:利用低 29 位
表示线程池中线程数,通过 高 3 位
表示线程池的运行状态:
RUNNING
:-1 << COUNT_BITS
,即高 3 位为 111
,该状态的线程池会接收新任务,并处理阻塞队列中的任务;SHUTDOWN
: 0 << COUNT_BITS
,即高3位为 000
,该状态的线程池不会接收新任务,但会处理阻塞队列中的任务;STOP
: 1 << COUNT_BITS
,即高3位为 001
,该状态的线程不会接收新任务,也不会处理阻塞队列中的任务,而且会中断正在运行的任务;TIDYING
: 2 << COUNT_BITS
,即高3位为 010
.中文是简洁,理解了中文就容易理解这个状态了。所有任务都已终止, workeCount
为零时,线程会转换到 TIDYING
状态,并将运行 terminate()
钩子方法。TERMINATED
: 3 << COUNT_BITS
,即高3位为 011
,terminate()
运行完成 ;Java 线程池提供了两种不同的方式来执行任务:
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
这种方式的任务需要实现 java.lang.Runnable
,没有返回值无法判断任务是否成功执行。
/**
* Submits a value-returning task for execution and returns a
* Future representing the pending results of the task. The
* Future's {@code get} method will return the task's result upon
* successful completion.
*
*
* If you would like to immediately block waiting
* for a task, you can use constructions of the form
* {@code result = exec.submit(aCallable).get();}
*
*
Note: The {@link Executors} class includes a set of methods
* that can convert some other common closure-like objects,
* for example, {@link java.security.PrivilegedAction} to
* {@link Callable} form so they can be submitted.
*
* @param task the task to submit
* @param the type of the task's result
* @return a Future representing pending completion of the task
* @throws RejectedExecutionException if the task cannot be
* scheduled for execution
* @throws NullPointerException if the task is null
*/
Future submit(Callable task);
这法方式任务需要实现 java.util.concurrent.Callable
是可以获取到任务执行后的返回值的。并且在 submit(Callable)
方法最终会执行到 execute(Runnable command)
。
下面我们就来分析一下 ThreadPoolExecutor#execute
的执行源码。
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();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
第一步,ctl 记录了线程池状态和线程数 ,workerCountOf方法根据ctl的低29位,得到线程池的当前线程数。首先会判断如果工作的线程数少于核心线程数,就会创建一个线程, addWorker 有两个请求参数,一个是任务对象,另外一个参数是 boolean 值,当这个值是 true 就会判断运行的线程数是否大于核心线程数。如果这个值是 false 的话就会判断运行的线程数是否大于最大线程数。如果添加任务成功的话就会直接返回。
第二步,首先会判断当前线程池是否在运行状态,如果当前线程池在运行中,这个时候运行的任务已经大于等于核心线程数,所以会把任务添加到阻塞队列当中。这个时候有可能线程池被停止了,所以它需要再次判断当前线程池是否正处于运行当中。如果当前线程池没有运行的话会就会把当前任务删除,并执行拒绝策略。workerCountOf(recheck) == 0
如果当前工作的线程数量为 0 的话,因为线程有可能会抛异常,导致这个线程停止。所以核心工作的线程有可能会减少。这个时候就需要创建 Worker 来执行线程。因为这个时候已经把任务提交到队列当中,这个时候如果工作 Worker 数为 0 的话就没有线程来执行任务。
第三步,这个时候工作线程数大于等于核心线程数,并且可能是线程池没有在运行状态或者阻塞队列已经添加执行的任务已经满了。 这个时候就需要增加工作 Worker 直到达到线程池的最大线程数。
第四步,在添加工作 Worker 的时候会返回一个 boolean 值,如果当前的运行的工作 Worker 已经等于最大线程数的时候就会执行拒绝策略。
添加任务逻辑如下:
ThreadPoolExecutor#addWord
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;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
core ? corePoolSize : maximumPoolSize
这个值就直接返回这个是添加工作任务的前个部分,下面我们来分析一下添加工作任务的另外一个部分。
ThreadPoolExecutor#addWord
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
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.
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();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
工作线程通过 Worker 实现了并且继承了并发工具类 ReentrantLock 可以线程安全的可工作线程添加到 HashSet 当中。在工作线程添加成功之后就会运行。
AbstractQueuedSynchronizer
, 可以使用里面的同步方法因为 Work 实现了 java.lang.Runnable
,所以当 Work 运行的时候。其实是执行它的 run()
方法。最终会调用 runWorker()
方法。
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 {
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);
}
}
runWorker
方法是线程池执行的核心:
Work#unlock
方法,允许当前的任务被 interrupts
。Work
当中的任务 firstTask
,并且设置当前 Work
的 firstTask
为空,以便 Work 执行完当前任务可以执行其它任务。当前任务会先执行 Work#lock
方法,并且最终会调用 Work#unlock
方法beforeExecute
和 任务执行后的 afterExecute
这两个钩子方法。下面我们来看一下线程池从阻塞队列里面获取任务
ThreadPoolExecutor#getTask
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
整个 getTask
操作在自旋下完成:
所以,线程池中实现的线程可以一直执行由用户提交的任务。
上面讲的是 Runnable 类型的任务,这个任务有一个缺点就是没有返回值,下面我们就来讲一下有返回值的任务。线程池还可以执行实现了 java.util.concurrent.Callable
,这个任务就可以有返回值。下面我们来比较一下 Runnable 与 Callable 这两个任务。
public class CallableDemo {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Future future = executorService.submit(new CallTask());
String result = future.get();
System.out.println(result);
executorService.shutdown();
}
private static class CallTask implements Callable {
@Override
public String call() {
return "this is a callable demo";
}
}
}
调用线程池 ThreadPoolExecutor#submit
会调用它的父类方法 AbstractExecutorService#submit
。
.AbstractExecutorService#submit
public Future submit(Callable task) {
if (task == null) throw new NullPointerException();
RunnableFuture ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这时的 Callable 任务会包装成 FutureTask
。
FutureTask.java
/**
* The run state of this task, initially NEW. The run state
* transitions to a terminal state only in methods set,
* setException, and cancel. During completion, state may take on
* transient values of COMPLETING (while outcome is being set) or
* INTERRUPTING (only while interrupting the runner to satisfy a
* cancel(true)). Transitions from these intermediate to final
* states use cheaper ordered/lazy writes because values are unique
* and cannot be further modified.
*
* Possible state transitions:
* NEW -> COMPLETING -> NORMAL
* NEW -> COMPLETING -> EXCEPTIONAL
* NEW -> CANCELLED
* NEW -> INTERRUPTING -> INTERRUPTED
*/
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;
上面就是 FutureTask
的所有状态,刚开始的时候是 NEW,它有几下如何状态机转换。
FutureTask 同时也实现了 Runnable 接口,在执行 submit(Runnable task)
方法的时候调用了 execute(Runnable command)
,最终其实是调用了 java.util.concurrent.FutureTask#run
方法。
下面就是 FutureTask#get
的具体代码。
FutureTask#get
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
当调用 FutureTask#get
会阻塞父线程,直到返回结果。具体实现如下:
/**
* Awaits completion or aborts on interrupt or timeout.
*
* @param timed true if use timed waits
* @param nanos time to wait, if timed
* @return state upon completion
*/
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)
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);
}
}
上面的逻辑如下:
下面就是有返回值 Callable 任务最终执行的代码。
FutureTask#run
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable 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);
}
}
FutureTask
的状态不是NEW
直接返回Callable#call
方法setException
方法保存异常FutureTask#setException
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
FutureTask#set
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
FutureTask#setException
和 FutureTask#set
,都会通过UnSAFE
修改 FutureTask
的状态,并执行finishCompletion
方法通知主线程任务已经执行完成;
FutureTask#finishCompletion
/**
* Removes and signals all waiting threads, invokes done(), and
* nulls out callable.
*/
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#get
方法时,会把主线程封装成 WaitNode
节点并保存在 waiters
链表中;FutureTask
任务执行完成后,通过 UNSAFE
设置 waiters
的值,并通过 LockSupport#unpark
方法唤醒主线程;