摘要:大家好呀,我是小米,一个31岁的Java开发,喜欢技术、喜欢撸代码、喜欢在公众号和大家一起交流成长的技术人。
大家好呀,我是小米,一个31岁的Java开发,喜欢技术、喜欢撸代码、喜欢在公众号和大家一起交流成长的技术人。
今天想和大家聊一个我最近在社招面试中遇到的真题:“FutureTask 详解”。
说实话,这个问题其实我之前也只是“会用”,但没怎么深入。
直到被面试官一问,差点当场社死!
也就是那次经历,逼着我硬着头皮去研究透彻,现在不光能讲明白原理,还能顺便给你来段手撸Demo!
既然我掉过坑,就带你避坑!
那是去年冬天,我准备跳槽,从一家本地小厂打算跳到一家互联网大厂。
面试官是个特别严谨的小哥,一身黑衣,戴着金丝眼镜,一看就是那种JVM和多线程都玩得很溜的人。
聊着聊着,面试官突然抛出一句:
你能详细讲讲 FutureTask 是怎么实现的,以及它和 Future、Callable、Runnable 的关系吗?
我:“emmm……”
虽然平时用过 FutureTask 做异步任务,但让我详细讲实现原理?
当场脑子一片空白,只能硬着头皮答了个概念性的回答。
我不服!回去撸了一晚上源码,从上到下,从实现到应用场景,从坑点到线程池结合,统统搞明白了。
今天就把我当初学到的,最清晰的版本,分享给你!
咱们先来捋清楚概念。
1、Future 是个接口,定义了异步任务的结果
作用就是:
get:阻塞等待异步任务结果返回cancel:取消任务isDone:判断任务是否完成isCancelled:判断是否被取消注意,Future 本身不能执行任务,只是用来保存任务结果。
2、FutureTask 是个实现类,既能执行又能保存结果
简单理解,FutureTask 既是 Runnable 又是 Future,可以被线程池或线程执行。
其中 RunnableFuture 是个接口,长这样:
也就是说,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:不打断正在执行的任务
核心是:
和线程池搭配用法
面试官:“FutureTask 是怎么实现的?”
我:“FutureTask 实现了 RunnableFuture 接口,既能作为 Runnable 被线程池执行,又实现了 Future 接口用来保存异步结果。内部依靠 state 状态和 CAS 保证线程安全,run 方法会调用 Callable.call 或 Runnable.run,执行完成后设置状态为 COMPLETING、NORMAL 或 EXCEPTIONAL。get 方法阻塞等待,内部通过 LockSupport 实现线程挂起和唤醒,cancel 方法则依据 mayInterruptIfRunning 参数决定是否中断线程,最终更新状态和唤醒阻塞线程。”
面试官点了点头:“不错,懂实现细节,也知道场景。你会用 CompletableFuture 吗?”
于是我们顺利进入了下一轮。
写在最后其实面试考 FutureTask,考的不是你会不会写,而是你是否理解背后的并发机制,是否能在合适的场景下正确选择方案。
如果你正好准备面试、或者想深入搞懂 Java 并发,不妨也动手撸一遍源码,写几个小 Demo,体会一下线程安全、CAS、阻塞唤醒、线程状态切换的魅力。
我相信你也会像我一样,越学越上头。
你的支持是我继续分享干货的最大动力!
来源:精彩教育