本文来源:
http://summerisgreen.com/blog/2019-03-03-2019-03-03-futuretask%E4%B8%AD%E7%9A%84%E7%8A%B6%E6%80%81%E6%9C%BA.html
由于FutureTask可能在多个线程中触发执行或者取消逻辑,如果没有线程安全保证, 可能会出现执行已取消操作,异步操作被多次执行等违反Futuretask语义的情形。为了保证FutureTask的线程安全,jdk结合使用了状态机和cas操作。
FutureTask中的状态机
状态机设计
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中,一共存在上面所述7种状态,其中New为实例的初始化状态, Completing和Interrupting为中间状态,其余四个为最终状态。
New->Completing->Normal
New->Completing->Exceptional
New->Cancelled
New->Interrupting->Interrupted
并发问题的解决
争夺状态改变权
对于初始化状态New,正如之前看到的,其可能有四种状态演化的方式, 而这四种状态演化方式主要是通过调用cancel,set和setException三个方法来获得的。
cancel操作: New->Interrupting->Interrupted
cancel操作: New->Cancel
set操作: New->Completing->Normal
setException操作: New->Completing->Exceptional
由于对FutureTask实例存在cancel,set,setException操作的并发调用, 为了避免状态演化的混乱,这三种方法均采用cas操作来争夺唯一的状态改变权。表现在源码中,这几个方法都通过调用STATE.compareAndSet来试图获取状态控制权。当获取到状态控制权之后,表面该线程是唯一修改状态变量的线程, 因此可以直接继续操作状态值(比如set方法中:STATE.setRelease(this, NORMAL)),而无需再使用对性能有消耗的cas操作了。
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW && STATE.compareAndSet
(this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
STATE.setRelease(this, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
状态码判断
FutureTask中状态机设计的比较巧妙的一点就是:可以基于状态码的值来快速决定下一步的行动。比如在get方法中,通过比较当前状态和COMPLETING值,来决定是继续等待完成还是调用report方法返回结果。对后者,通过将状态值与NORMAL以及CANCEL值比较,得出最终结果或异常类型。
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
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);
}
任务执行
FutureTask中最重要的方法莫过于run方法了,毕竟FutureTask的核心就是异步执行了。在很多情况下提交的Futuretask任务为耗时任务,在该任务执行前或执行过程中极有可能由于各种原因被取消。因此在run方法的finally中需要进行状态机判断,如果该任务已被取消,需要让出当前线程,等完成取消任务后再继续执行。
public void run() {
if (state != NEW ||
!RUNNER.compareAndSet(this, 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);
}
}
private void handlePossibleCancellationInterrupt(int s) {
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
}
值得注意的是,在任务结束后,futuretask类只保留了最终的状态及结果(正确值或异常), 而执行线程信息以及callable回调函数等对象都因不再有用而置null,以便及时被gc回收。
结果等待
当用户代码向线程池提交任务后,一个最常见的后续操作就是阻塞(或带超时的阻塞)等待任务结果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
如果此时任务还未完成,其将调用awaitDone方法,利用cas加入队列后,使用park/parkNanos方法阻塞线程,直至被唤醒。其将在下面三种情况下被唤醒: 1.线程被中断 2.超时阻塞已超时 3.完成任务结果及状态记录(正确值或异常结果更新)
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);
}
}
● Code Review最佳实践
● jedis用过没?讲讲原理呗
● Spring容器启动@Value属性无法注入?
● 聊聊Spring Bean实例化
● Spring IOC流程清楚不?聊聊看
● 面试官:ScheduleThreadPoolExecutor了解不?
● 配置中心只有Apollo么?看看点评的Lion
● 面试官说从源码角度说说Java线程池
● JedisPool连接池相关配置
● Hystrix初探
● 别再关注删库跑路了,谈谈数据库架构
● 那些年非常火的MyCAT是什么?
● Java14带来了许多新功能