futuretask使用_面试官:JUC 中的FutureTask原理你有了解不?

点击上方☝ SpringForAll社区  轻松关注! 及时获取有趣有料的技术文章

本文来源:

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);

}

}

23e79d69bd363a98a0497d162d257f59.gif

● Code Review最佳实践

● jedis用过没?讲讲原理呗

● Spring容器启动@Value属性无法注入?

● 聊聊Spring Bean实例化

● Spring IOC流程清楚不?聊聊看

● 面试官:ScheduleThreadPoolExecutor了解不?

● 配置中心只有Apollo么?看看点评的Lion

● 面试官说从源码角度说说Java线程池

● JedisPool连接池相关配置

● Hystrix初探

● 别再关注删库跑路了,谈谈数据库架构

● 那些年非常火的MyCAT是什么?

● Java14带来了许多新功能

bf380e33f7b6f592160ba38c0f39a12c.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值