大家好呀,我是小米,一个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 应用场景异步计算任务
带返回值的并发任务
多个任务并发执行,最后统一汇总结果
和线程池搭配用法
多次执行问题: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如果你觉得这篇文章对你有用,记得请关注我,点赞、在看、转发三连支持一下哦!
你的支持是我继续分享干货的最大动力!