引言:
我们先回顾一下future的使用:
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交一个Callable任务
Future<Integer> future = executor.submit(new MyTask());
// 在后台执行其他操作
System.out.println("do other operations in main Thread");
// 获取任务结果
try {
int result = future.get();
System.out.println("Task result: " + result);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
// 关闭线程池
executor.shutdown();
问题:future.get()会阻塞调用线程,那么它是如何阻塞的呢?在future执行完毕它又是如何被唤醒的呢?
猜想:线程的阻塞是在future.get()中完成的,而线程的唤醒是在异步线程执行完成后进行的。
任务1:future.get()是如何阻塞线程的?
任务2:在异步线程执行完成之后是如何唤醒线程的?
任务1:跟踪future.get()找到它是如何阻塞线程的
先来看下future.get()接口的定义:
// Future.java
/**
* 等待计算完成,然后检索其结果。
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an
* exception
* @throws InterruptedException if the current thread was interrupted
* while waiting
*/
V get() throws InterruptedException, ExecutionException;
get的作用主要是等待计算完成,然后返回计算结果。可能抛出三种异常。
接下来找到future的实现类,来看一下get方法的具体实现。
跟踪 Future<Integer> future = executor.submit(new MyTask()); 方法,查看返回的future的具体类型。
executor.submit() 同样是一个接口,我们需要找到方法的具体实现。
跟踪 ExecutorService executor = Executors.newFixedThreadPool(1); 方法,查看executor的具体类型。
// Executors.java
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
executor的具体类型是ThreadPoolExecutor,接下来查看它对submit方法的实现来找到future的类型。
在跟踪过程中发现ThreadPoolExecutor并不直接实现submit()方法,这个方法在它继承的抽象父类AbstractExecutorService.java中实现:
// AbstractExecutorService.java
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
这里可以看到submit方法返回的Future的类型是一个RunnableFuture类型的接口,还不是具体类型,需要继续查看RunnableFuture的具体类型是什么,才能找到get方法的具体实现。
跟踪 RunnableFuture<T> ftask = newTaskFor(task); 方法查看ftask的具体类型
// AbstractExecutorService.java
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
找到ftask的具体类型是一个FutureTask,也是future的具体类型,接下来查看FutureTask中get()方法的具体实现,来找到它是如何阻塞当前线程的。
// 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; // 任务已经被中断了
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
在这里我们发现FutureTask的实例有很多状态,在get()方法中,当状态为未执行或正在执行时,会调用s = awaitDone(false, 0L); 方法,根据方法名可以猜想阻塞是在这个方法中完成的,所以我们跟踪下这个方法。
// FutureTask.java
/** Treiber stack of waiting threads */
private volatile WaitNode waiters;
/**
* Simple linked list nodes to record waiting threads in a Treiber
* stack. See other classes such as Phaser and SynchronousQueue
* for more detailed explanation.
*/
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
/**
* 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);
}
}
在这个方法中,正常执行过程中,在无限循环中会依次做以下几件事:
- 判断等待线程是否被打断,如果被打断则抛出异常并移除等待节点
- 判断future的状态是否已经完成,如果已经完成,则将等待节点的线程置空。(等待节点是一个栈型结构,属性有线程和指向另一个等待节点的指针。如果future尚未执行完成,则调用Thread.yield(); 让出cpu时间片,将线程转换为就绪状态。
- 如果等待节点为空则新建一个等待节点;
- 使用CAS的方式将等待节点压入等待节点栈中(如果还没有压入的情况下)
- 如果设置了等待时间,在超时后会移除等待节点并返回状态,没有超时的情况下调用LockSupport.parkNanos(this, nanos);将当前线程阻塞到等待时间结束。
- 如果没有设置等待时间,则会调用LockSupport.park(this); 阻塞当前线程,直到线程被打断或调用LockSupport.unPark() 进行唤醒。
在这个方法中,我们可以观察到它创建了一个包含当前线程的等待节点,并通过UNSAFE的比较和交换方法将它压入了future的等待节点栈中,随后调用了LockSupport.park(this)方法阻塞了当前线程。所以说future.get() 是通过LockSupport.park(this) 方法阻塞当前线程的。并且在线程被唤醒后可以返回结果或者错误。那么线程是如何被唤醒的呢?我们开始探究在异步线程执行完成后是如何唤醒线程的。
任务2:探究在异步线程执行完成之后是如何唤醒线程的
因为我们将任务交给线程池执行,而线程池又将任务包装为一个FutureTask,FutureTask实现了RunnableFuture,RunnableFuture继承自Runnable,并且线程的执行一定会调用Runnable接口中的run方法,所以我们从FutureTask的run方法开始跟踪。
// FutureTask.java
/** The underlying callable; nulled out after running */
private Callable<V> callable;
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Callable}.
*
* @param callable the callable task
* @throws NullPointerException if the callable is null
*/
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
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);
}
}
在这个方法中,Callable<V> c = callable; 就是我们在创建FutureTask时传入的参数callable
// AbstractExecutorService.java
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
继续看run方法
// FutureTask.java run方法中的内容
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);
}
在对callable和state进行检查后,run方法开始执行call()方法中的内容,然后将是否执行完成的表示ran置为true;如果执行成功则会执行set(result),出现异常则会执行setException(ex),到这里已经找到了异步执行完成的后处理方法,唤醒调用线程的操作应该就在这里,我们跟踪进这两个方法中。
// FutureTask.java
/**
* Sets the result of this future to the given value unless
* this future has already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon successful completion of the computation.
*
* @param v the value
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
/**
* Causes this future to report an {@link ExecutionException}
* with the given throwable as its cause, unless this future has
* already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon failure of the computation.
*
* @param t the cause of failure
*/
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
在这两个方法中,首先将state设置为待完成,然后将结果或异常赋值给outcom对象,之后设置状态为完成或异常,之后调用finishCompletion()方法。唤醒调用线程的操作无论是异常还是正常都需要执行,所以继续跟踪。
// FutureTask.java
/**
* 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
}
在这个方法中,对waiters进行了遍历,waiters也保存了等待节点的栈,在这里依次对所有的等待节点进行唤醒,对所有等待节点中的线程执行 LockSupport.unpark(t);,方法进行唤醒。至此第二个问题也弄清楚了,异步线程执行完成之后会调用LockSupport.unpark(t)唤醒所有等待它执行完成的线程。
线程唤醒后,awaitDone方法将返回FutureTask的状态给get方法,接下来get方法会调用report(s) 方法包装outcom并返回,或抛出异常。
// FutureTask.java
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
结论:
future.get() 通过调用LockSupport.park() 方法将线程阻塞并创建等待节点WaitNode保存在future对象中,在将future交给线程池执行后future实现的run方法被执行,run方法在callable的call方法执行完毕后会将结果存入outcom中并遍历waiters中的所有等待节点获取阻塞的线程并调用LockSupport.unpark()方法进行唤醒。get() 方法被唤醒后会根据future的状态包装outcom并返回。
延伸:
那么什么是LockSupport.park()和unPark()呢?
// LockSupport.java
/**
* Disables the current thread for thread scheduling purposes unless the
* permit is available.
*
* <p>If the permit is available then it is consumed and the call returns
* immediately; otherwise
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until one of three things happens:
*
* <ul>
* <li>Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or
*
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or
*
* <li>The call spuriously (that is, for no reason) returns.
* </ul>
*
* <p>This method does <em>not</em> report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.
*
* @param blocker the synchronization object responsible for this
* thread parking
* @since 1.6
*/
public static void park(Object blocker) {
Thread t = Thread.currentThread();
// 设置线程的阻塞对象,便于调试和监控线程的状态。
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
}
调用Unsafe.getUnsafe().park(false, 0L);
// Unsafe.java
/**
* Block current thread, returning when a balancing
* <tt>unpark</tt> occurs, or a balancing <tt>unpark</tt> has
* already occurred, or the thread is interrupted, or, if not
* absolute and time is not zero, the given time nanoseconds have
* elapsed, or if absolute, the given deadline in milliseconds
* since Epoch has passed, or spuriously (i.e., returning for no
* "reason"). Note: This operation is in the Unsafe class only
* because <tt>unpark</tt> is, so it would be strange to place it
* elsewhere.
*/
public native void park(boolean isAbsolute, long time);
native方法:阻塞当前线程,当unpark发生时返回,或者已经发生了unpark时返回(start后但在park之前调用了unpark,或者线程被打断,如果absolute为false且时间不为0,则相对的时间过去会返回,如果absolute为true,则给定的deadline是从纪元来时的时间戳,或虚假唤醒(没有理由的情况下返回)注意!Unsafe类不能直接调用,会报安全异常。
注意LockSupport.park()可能出现虚假唤醒的情况,唤醒后应检查是否满足唤醒条件,如果不满足应继续调用park()。
// LockSupport.java
/**
* Makes available the permit for the given thread, if it
* was not already available. If the thread was blocked on
* {@code park} then it will unblock. Otherwise, its next call
* to {@code park} is guaranteed not to block. This operation
* is not guaranteed to have any effect at all if the given
* thread has not been started.
*
* @param thread the thread to unpark, or {@code null}, in which case
* this operation has no effect
*/
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
调用后如果线程处于阻塞状态则会被唤醒,如果线程不在阻塞状态,则下一次的park调用将不会被阻塞。如果线程没有启动则不保证有效果。
总结:LockSupport.lock(this) 和 LockSupport.unlock(t) 为并发编程提供了一种高效可靠的阻塞和唤醒机制。