如果程序运行正常,通常调用 get() 方法,会将当前线程挂起,那谁来唤醒呢?自然是 run() 方法运行完会唤醒,设置返回结果(set方法)/异常的方法(setException方法) 两个方法中都会调用 finishCompletion 方法,该方法就会唤醒等待队列中的线程
private void finishCompletion() { // assert state > COMPLETING; for (WaitNode q; (q = waiters) != null;) { if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) { for (;;) { Thread t = q.thread; if (t != null) { q.thread = null; // 唤醒等待队列中的线程 LockSupport.unpark(t); } WaitNode next = q.next; if (next == null) break; q.next = null; // unlink to help gc q = next; } break; } } done(); callable = null; // to reduce footprint }将一个任务的状态设置成终止态只有三种方法:
set
setException
cancel
前两种方法已经分析完,接下来我们就看一下 cancel 方法
查看 Future cancel(),该方法注释上明确说明三种 cancel 操作一定失败的情形
任务已经执行完成了
任务已经被取消过了
任务因为某种原因不能被取消
其它情况下,cancel操作将返回true。值得注意的是,cancel操作返回 true 并不代表任务真的就是被取消, 这取决于发动cancel状态时,任务所处的状态
如果发起cancel时任务还没有开始运行,则随后任务就不会被执行;
如果发起cancel时任务已经在运行了,则这时就需要看 mayInterruptIfRunning 参数了:
如果mayInterruptIfRunning 为true, 则当前在执行的任务会被中断
如果mayInterruptIfRunning 为false, 则可以允许正在执行的任务继续运行,直到它执行完
有了这些铺垫,看一下 cancel 代码的逻辑就秒懂了
public boolean cancel(boolean mayInterruptIfRunning) { if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, 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 // 修改为最终状态 INTERRUPTED UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); } } } finally { // 唤醒等待中的线程 finishCompletion(); } return true; }核心方法终于分析完了,到这咱们喝口茶休息一下吧
我是想说,使用 FutureTask 来演练烧水泡茶经典程序
如上图:
洗水壶 1 分钟
烧开水 15 分钟
洗茶壶 1 分钟
洗茶杯 1 分钟
拿茶叶 2 分钟
最终泡茶
让我心算一下,如果串行总共需要 20 分钟,但很显然在烧开水期间,我们可以洗茶壶/洗茶杯/拿茶叶
这样总共需要 16 分钟,节约了 4分钟时间,烧水泡茶尚且如此,在现在高并发的时代,4分钟可以做的事太多了,学会使用 Future 优化程序是必然(其实优化程序就是寻找关键路径,关键路径找到了,非关键路径的任务通常就可以和关键路径的内容并行执行了)
@Slf4j public class MakeTeaExample { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); // 创建线程1的FutureTask FutureTask<String> ft1 = new FutureTask<String>(new T1Task()); // 创建线程2的FutureTask FutureTask<String> ft2 = new FutureTask<String>(new T2Task()); executorService.submit(ft1); executorService.submit(ft2); log.info(ft1.get() + ft2.get()); log.info("开始泡茶"); executorService.shutdown(); } static class T1Task implements Callable<String> { @Override public String call() throws Exception { log.info("T1:洗水壶..."); TimeUnit.SECONDS.sleep(1); log.info("T1:烧开水..."); TimeUnit.SECONDS.sleep(15); return "T1:开水已备好"; } } static class T2Task implements Callable<String> { @Override public String call() throws Exception { log.info("T2:洗茶壶..."); TimeUnit.SECONDS.sleep(1); log.info("T2:洗茶杯..."); TimeUnit.SECONDS.sleep(2); log.info("T2:拿茶叶..."); TimeUnit.SECONDS.sleep(1); return "T2:福鼎白茶拿到了"; } } }上面的程序是主线程等待两个 FutureTask 的执行结果,线程1 烧开水时间更长,线程1希望在水烧开的那一刹那就可以拿到茶叶直接泡茶,怎么半呢?