社招必问!FutureTask底层原理+应用场景,带你一次学会

软件求生 2025-04-20 10:39:44



大家好呀,我是小米,一个31岁的Java开发,喜欢技术、喜欢撸代码、喜欢在公众号和大家一起交流成长的技术人。

今天想和大家聊一个我最近在社招面试中遇到的真题:“FutureTask 详解”。

说实话,这个问题其实我之前也只是“会用”,但没怎么深入。

直到被面试官一问,差点当场社死!

也就是那次经历,逼着我硬着头皮去研究透彻,现在不光能讲明白原理,还能顺便给你来段手撸Demo!

既然我掉过坑,就带你避坑!

【故事开场】那年社招,我差点卡在 FutureTask 上

那是去年冬天,我准备跳槽,从一家本地小厂打算跳到一家互联网大厂。

面试官是个特别严谨的小哥,一身黑衣,戴着金丝眼镜,一看就是那种JVM和多线程都玩得很溜的人。

聊着聊着,面试官突然抛出一句:

你能详细讲讲 FutureTask 是怎么实现的,以及它和 Future、Callable、Runnable 的关系吗?

我:“emmm……”

虽然平时用过 FutureTask 做异步任务,但让我详细讲实现原理?

当场脑子一片空白,只能硬着头皮答了个概念性的回答。

面试官点了点头,意味深长地说:“回去可以好好看看源码。”

我不服!回去撸了一晚上源码,从上到下,从实现到应用场景,从坑点到线程池结合,统统搞明白了。

今天就把我当初学到的,最清晰的版本,分享给你!

什么是 Future 和 FutureTask?

咱们先来捋清楚概念。

1、Future 是个接口,定义了异步任务的结果

作用就是:

get():阻塞等待异步任务结果返回

cancel():取消任务

isDone():判断任务是否完成

isCancelled():判断是否被取消

注意,Future 本身不能执行任务,只是用来保存任务结果。

2、FutureTask 是个实现类,既能执行又能保存结果

简单理解,FutureTask 既是Runnable又是Future,可以被线程池或线程执行。

其中 RunnableFuture 是个接口,长这样:

也就是说,FutureTask 既是任务,又是结果容器。

FutureTask 核心结构和原理

1、构造方法

支持传入Callable,执行带返回值任务

也支持Runnable,但会把 result 作为最终返回值(一般不推荐)

2、内部状态控制

FutureTask 依靠一个state变量来控制任务状态。

状态定义:

这些状态之间的切换,靠CAS保证线程安全。

3、run() 方法执行流程

run() 是 FutureTask 核心逻辑:

这个方法做了几件事:

判断当前状态是否是 NEW,如果不是直接返回(避免重复执行)

设置执行线程 runner

执行callable.call()

执行完成:

正常:set(result)

异常:setException(ex)

runner 置空

保证任务只会执行一次,且线程安全。

4、get() 方法阻塞原理

FutureTask 内部有个等待队列,当 get() 被调用,如果任务未完成,会把调用线程加入等待队列,挂起。

任务完成后,唤醒所有等待线程,返回结果。

用到了 LockSupport.park() 和 unpark() 实现线程阻塞和唤醒。

5、取消机制 cancel()

作用:

mayInterruptIfRunning = true:如果任务正在执行,尝试中断false:不打断正在执行的任务

核心是:

改变 state

中断 runner 线程(如果允许)

FutureTask 应用场景

异步计算任务

带返回值的并发任务

多个任务并发执行,最后统一汇总结果

和线程池搭配用法

FutureTask vs 其它方案

FutureTask 常见坑点

多次执行问题:run() 方法内部用 CAS 保证只执行一次。

get() 阻塞问题:调用 get() 会阻塞,建议放到子线程里或者超时 get。

取消不一定生效:cancel() 方法取决于 mayInterruptIfRunning 参数和线程响应中断。

我当时是这样回答的

面试官:“FutureTask 是怎么实现的?”

我:“FutureTask 实现了 RunnableFuture 接口,既能作为 Runnable 被线程池执行,又实现了 Future 接口用来保存异步结果。内部依靠 state 状态和 CAS 保证线程安全,run() 方法会调用 Callable.call() 或 Runnable.run(),执行完成后设置状态为 COMPLETING、NORMAL 或 EXCEPTIONAL。get() 方法阻塞等待,内部通过 LockSupport 实现线程挂起和唤醒,cancel() 方法则依据 mayInterruptIfRunning 参数决定是否中断线程,最终更新状态和唤醒阻塞线程。”

面试官点了点头:“不错,懂实现细节,也知道场景。你会用 CompletableFuture 吗?”

于是我们顺利进入了下一轮。

写在最后

其实面试考 FutureTask,考的不是你会不会写,而是你是否理解背后的并发机制,是否能在合适的场景下正确选择方案。

如果你正好准备面试、或者想深入搞懂 Java 并发,不妨也动手撸一遍源码,写几个小 Demo,体会一下线程安全、CAS、阻塞唤醒、线程状态切换的魅力。

我相信你也会像我一样,越学越上头。

END

如果你觉得这篇文章对你有用,记得请关注我,点赞、在看、转发三连支持一下哦!

你的支持是我继续分享干货的最大动力!

0 阅读:0

软件求生

简介:从事软件开发,分享“技术”、“运营”、“产品”等。