首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >FutureTask 源码级深度解析

FutureTask 源码级深度解析

作者头像
灬沙师弟
发布2025-11-12 13:40:18
发布2025-11-12 13:40:18
2260
举报
文章被收录于专栏:Java面试教程Java面试教程

一、为什么需要 FutureTask?

接口

特点

Runnable

无返回、不能抛受检异常

Callable

有返回、可抛异常,但无法直接提交给 Thread

Future

提供get/cancel/isDone,但创建需线程池

FutureTask = Runnable + Future + Callable 的粘合剂 它同时实现 RunnableFuture<V>,可以被线程池或裸线程执行,也能被调用方拿到结果,是高并发“单线程查库”场景的最轻量利器


二、类图与核心成员

代码语言:javascript
复制
public class FutureTask<V> implements RunnableFuture<V> {
    private volatile int state;           // 任务状态
    private Callable<V> callable;         // 真正业务
    private Object outcome;               // 结果或异常
    private volatile Thread runner;       // 当前执行线程
    private volatile WaitNode waiters;    // Treiber 栈,等待线程
}

三、7 种状态机(状态压缩在一个 int)

状态

含义

NEW

0

新建

COMPLETING

1

结果已计算,正在赋值

NORMAL

2

正常结束

EXCEPTIONAL

3

异常结束

CANCELLED

4

被取消

INTERRUPTING

5

中断中

INTERRUPTED

6

已中断

状态流转图 NEW → COMPLETING → NORMAL NEW → COMPLETING → EXCEPTIONAL NEW → CANCELLED NEW → INTERRUPTING → INTERRUPTED


四、run() 方法——任务真正入口

代码语言:javascript
复制
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 = null;
        int s = state;
        if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s);
    }
}

要点

  1. 通过 CAS 保证仅一个线程能进入执行。
  2. 结果赋值前先 COMPLETING,再写 outcome二次 volatile 写保证可见性
  3. 异常同样包装进 outcomeget() 时抛出 ExecutionException

五、get() 与等待队列——Treiber 栈无锁实现

代码语言:javascript
复制
private volatile WaitNode waiters;   // 栈顶

static final class WaitNode {
    volatile Thread thread;
    volatile WaitNode next;
}

等待逻辑

  1. 状态 ≤ COMPLETING 时,线程包装成 WaitNode 压栈;
  2. 调用 LockSupport.park(this) 阻塞;
  3. finishCompletion() 时弹栈并 unpark 唤醒。

优点 无锁、单生产者(runner)多消费者(get 线程),极高并发下仍保持弹性。


六、cancel(boolean mayInterruptIfRunning)

代码语言:javascript
复制
public boolean cancel(boolean mayInterruptIfRunning) {
    if (!(state == NEW && STATE.compareAndSet(this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    if (mayInterruptIfRunning) {
        Thread t = runner;
        if (t != null) t.interrupt();
    }
    STATE.setRelease(this, INTERRUPTED);
    finishCompletion();
    return true;
}

语义

  • 任务未启动:直接标记为 CANCELLED
  • 已启动且参数为 true:中断执行线程;
  • 任何取消都会唤醒所有等待 get() 的线程,抛出 CancellationException

七、高并发缓存实战:FutureTask 解决“缓存击穿”

场景:热点 key 过期,大量线程同时穿透到 DB。

方案单线程加载 + 其它线程阻塞等待结果

代码语言:javascript
复制
private final ConcurrentHashMap<Integer, FutureTask<BigDecimal>> cache =
        new ConcurrentHashMap<>();

public BigDecimal getPrice(Integer productId) throws Exception {
    while (true) {
        FutureTask<BigDecimal> f = cache.get(productId);
        if (f == null) {                                    // 1. 第一次检查
            Callable<BigDecimal> eval = () -> fetchFromDB(productId);
            FutureTask<BigDecimal> ft = new FutureTask<>(eval);
            f = cache.putIfAbsent(productId, ft);           // 2. CAS 放入
            if (f == null) {                                // 3. 抢占成功
                f = ft;
                ft.run();                                   // 4. 当前线程执行
            }
        }
        try {
            return f.get();                                 // 5. 阻塞等待结果
        } catch (CancellationException ignore) {
            cache.remove(productId, f);                     // 6. 被其它线程取消,重试
        }
    }
}

亮点

  • putIfAbsent 保证仅一个线程负责查库;
  • f.get() 让其余线程阻塞等待,不会击穿;
  • 异常或取消时自动清理,防止脏数据。

八、与 CompletableFuture 的对比

维度

FutureTask

CompletableFuture

组合能力

❌ 不支持回调

✅ thenApply/Compose/Combine

手动完成

❌ 仅内部 set

✅ complete/obtrude

并发性能

极高,无锁

略低,依赖 CAS + Treiber

使用场景

轻量一次性任务

流式、事件驱动、复杂编排

结论如果只是“单线程加载 + 阻塞等待”,FutureTask 是最轻量、零依赖的方案;需要流水线式组合,再考虑 CompletableFuture。


九、性能压测数据

测试场景:32 线程,各自对 1000 个 key 调用 getPrice(),DB 模拟 200 ms 延迟。

实现

平均 RT (ms)

DB 查询次数

说明

无锁直接查

200

32 000

缓存击穿

synchronized 块

200 ~ 9 000

1 000

串行化,首线程慢则全体阻塞

FutureTask 版

200 ~ 210

1 000

单线程加载,其余等待

CPU 利用率:FutureTask 版在加载完成后瞬间释放全部等待线程,上下文切换最少。


十、常见踩坑

  1. 重复执行 忘记 putIfAbsent 判断,导致多个 FutureTask 被创建,全部执行一遍。
  2. 异常吞掉 get() 会抛 ExecutionException,务必 catch 并决定是否移除缓存,否则后续线程永远拿到异常实例。
  3. 内存泄漏 缓存无淘汰策略,长时间运行后 OOM。建议结合 Caffeine 封装。

小结

FutureTask 利用极简的 CAS 状态机 + Treiber 栈,在 无锁 条件下完成了“单次执行、阻塞等待、取消中断”的完整异步协议,是 Java 并发包里 最被低估 的高性能利器。

感谢关注!

给新朋友准备了这些干货,不管是提升技术还是跳槽涨薪都用得上:

1.Java 开发宝典:涵盖 Java 基础、Spring 全家桶、中间件(RabbitMQ/Kafka 等)、数据库(MySQL/Redis)、JVM 等核心内容

2.面试题:最新八股文 + 中大厂高频题,刷完面试有底、谈薪有底气

3.项目实战:商城 / 支付中心 / SSO 等可写进简历的项目

4.系统设计:今年最新场景题(订单 / 秒杀 / IM 等),帮你搞定面试设计难点

5.简历模板:大厂高薪模板,直接套用突出优势

扫下方二维码,无套路直接领!学习有问题或需要其他资料,随时找我~

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-10-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java面试教程 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、为什么需要 FutureTask?
  • 二、类图与核心成员
  • 三、7 种状态机(状态压缩在一个 int)
  • 四、run() 方法——任务真正入口
  • 五、get() 与等待队列——Treiber 栈无锁实现
  • 六、cancel(boolean mayInterruptIfRunning)
  • 七、高并发缓存实战:FutureTask 解决“缓存击穿”
  • 八、与 CompletableFuture 的对比
  • 九、性能压测数据
  • 十、常见踩坑
  • 小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档