不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!(下)

简介: 不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!(下)

将一个任务的状态设置成终止态只有三种方法:


  • set


  • setException


  • cancel


前两种方法已经分析完,接下来我们就看一下 cancel 方法


查看 Future cancel(),该方法注释上明确说明三种 cancel 操作一定失败的情形


  1. 任务已经执行完成了


  1. 任务已经被取消过了


  1. 任务因为某种原因不能被取消


其它情况下,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;
}


核心方法终于分析完了,到这咱们喝口茶休息一下吧


微信图片_20220511120303.png


我是想说,使用 FutureTask 来演练烧水泡茶经典程序


微信图片_20220511120325.png


如上图:


  • 洗水壶 1 分钟


  • 烧开水 15 分钟


  • 洗茶壶 1 分钟


  • 洗茶杯 1 分钟


  • 拿茶叶 2 分钟


最终泡茶


让我心算一下,如果串行总共需要 20 分钟,但很显然在烧开水期间,我们可以洗茶壶/洗茶杯/拿茶叶


微信图片_20220511120426.png


这样总共需要 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希望在水烧开的那一刹那就可以拿到茶叶直接泡茶,怎么半呢?


微信图片_20220511120517.png


那只需要在线程 1 的FutureTask 中获取 线程 2 FutureTask 的返回结果就可以了,我们稍稍修改一下程序:


@Slf4j
public class MakeTeaExample1 {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
      ExecutorService executorService = Executors.newFixedThreadPool(2);
      // 创建线程2的FutureTask
      FutureTask<String> ft2 = new FutureTask<String>(new T2Task());
      // 创建线程1的FutureTask
      FutureTask<String> ft1 = new FutureTask<String>(new T1Task(ft2));
      executorService.submit(ft1);
      executorService.submit(ft2);
      executorService.shutdown();
   }
   static class T1Task implements Callable<String> {
      private FutureTask<String> ft2;
      public T1Task(FutureTask<String> ft2) {
         this.ft2 = ft2;
      }
      @Override
      public String call() throws Exception {
         log.info("T1:洗水壶...");
         TimeUnit.SECONDS.sleep(1);
         log.info("T1:烧开水...");
         TimeUnit.SECONDS.sleep(15);
         String t2Result = ft2.get();
         log.info("T1 拿到T2的 {}, 开始泡茶", t2Result);
         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 "福鼎白茶";
      }
   }
}


来看程序运行结果:


微信图片_20220511120605.png


知道这个变化后我们再回头看 ExecutorService 的三个 submit 方法:


<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);


第一种方法,逐层代码查看到这里:


微信图片_20220511120659.png


你会发现,和我们改造烧水泡茶的程序思维是相似的,可以传进去一个 result,result 相当于主线程和子线程之间的桥梁,通过它主子线程可以共享数据


第二个方法参数是 Runnable 类型参数,即便调用 get() 方法也是返回 null,所以仅是可以用来断言任务已经结束了,类似 Thread.join()


第三个方法参数是 Callable 类型参数,通过get() 方法可以明确获取 call() 方法的返回值

到这里,关于 Future 的整块讲解就结束了,还是需要简单消化一下的


微信图片_20220511120742.png


总结


如果熟悉 Javascript 的朋友,Future 的特性和 Javascript 的 Promise 是类似的,私下开玩笑通常将其比喻成男朋友的承诺


回归到Java,我们从 JDK 的演变历史,谈及 Callable 的诞生,它弥补了 Runnable 没有返回值的空缺,通过简单的 demo 了解 Callable 与 Future 的使用。 FutureTask 又是 Future接口的核心实现类,通过阅读源码了解了整个实现逻辑,最后结合FutureTask 和线程池演示烧水泡茶程序,相信到这里,你已经可以轻松获取线程结果了


烧水泡茶是非常简单的,如果更复杂业务逻辑,以这种方式使用 Future 必定会带来很大的会乱(程序结束没办法主动通知,Future 的链接和整合都需要手动操作)为了解决这个短板,没错,又是那个男人 Doug Lea, CompletableFuture 工具类在 Java1.8 的版本出现了,搭配 Lambda 的使用,让我们编写异步程序也像写串行代码那样简单,纵享丝滑


微信图片_20220511120816.png


接下来我们就了解一下 CompletableFuture 的使用


灵魂追问


  1. 你在日常开发工作中是怎样将整块任务做到分工与协作的呢?有什么基本准则吗?


  1. 如何批量的执行异步任务呢?



相关文章
|
5月前
|
缓存 Java 调度
Java并发编程:深入解析线程池与Future任务
【7月更文挑战第9天】线程池和Future任务是Java并发编程中非常重要的概念。线程池通过重用线程减少了线程创建和销毁的开销,提高了资源利用率。而Future接口则提供了检查异步任务状态和获取任务结果的能力,使得异步编程更加灵活和强大。掌握这些概念,将有助于我们编写出更高效、更可靠的并发程序。
|
3月前
|
Java
JAVA并发编程系列(13)Future、FutureTask异步小王子
本文详细解析了Future及其相关类FutureTask的工作原理与应用场景。首先介绍了Future的基本概念和接口方法,强调其异步计算特性。接着通过FutureTask实现了一个模拟外卖订单处理的示例,展示了如何并发查询外卖信息并汇总结果。最后深入分析了FutureTask的源码,包括其内部状态转换机制及关键方法的实现原理。通过本文,读者可以全面理解Future在并发编程中的作用及其实现细节。
|
7月前
|
Java 调度
Java一分钟之线程池:ExecutorService与Future
【5月更文挑战第12天】Java并发编程中,`ExecutorService`和`Future`是关键组件,简化多线程并提供异步执行能力。`ExecutorService`是线程池接口,用于提交任务到线程池,如`ThreadPoolExecutor`和`ScheduledThreadPoolExecutor`。通过`submit()`提交任务并返回`Future`对象,可检查任务状态、获取结果或取消任务。注意处理`ExecutionException`和避免无限等待。实战示例展示了如何异步执行任务并获取结果。理解这些概念对提升并发性能至关重要。
151 5
|
7月前
|
安全 Java
【亮剑】Java中的`Future`接口代表异步计算结果,常与`ExecutorService`配合启动任务并获取结果
【4月更文挑战第30天】Java中的`Future`接口代表异步计算结果,常与`ExecutorService`配合启动任务并获取结果。`Future`接口提供`isDone()`、`get()`、`get(timeout, unit)`和`cancel(mayInterruptIfRunning)`等方法。`FutureTask`是`Future`的实现类,可作为`Runnable`执行并返回结果。
75 1
|
7月前
|
Java
Java 并发编程:深入理解 ExecutorService 和 Future
【5月更文挑战第29天】本文将深入探讨 Java 中的 ExecutorService 和 Future,这两个在并发编程中非常重要的概念。我们将详细解释他们的作用,如何使用,以及他们的一些高级用法。通过本文,你将能够更好地理解和使用 Java 的并发工具,提高你的编程效率和代码质量。
|
7月前
|
Java
Java并发编程:理解并使用Future和Callable接口
【2月更文挑战第25天】 在Java中,多线程编程是一个重要的概念,它允许我们同时执行多个任务。然而,有时候我们需要等待一个或多个线程完成,然后才能继续执行其他任务。这就需要使用到Future和Callable接口。本文将深入探讨这两个接口的用法,以及它们如何帮助我们更好地管理多线程。
|
7月前
|
Java API
java多线程之FutureTask、Future、CompletableFuture
java多线程之FutureTask、Future、CompletableFuture
310 0
|
7月前
|
Java
深入理解 Java 异步编程:Future 和 CompletableFuture 的全面比较
深入理解 Java 异步编程:Future 和 CompletableFuture 的全面比较
310 0
|
7月前
|
Java
Java 并发编程 Future及CompletionService
Java 并发编程 Future及CompletionService `Future`用于异步结果计算。它提供了一些方法来检查计算是否完成,使用`get`方法将阻塞线程直到结果返回 `CompletionService`整合了`Executor`和`BlockingQueue`的功能。将`Callable`任务提交给它去执行,使用`take()`和`poll()`获取最新完成的任务执行结果.
Java 并发编程 Future及CompletionService
|
7月前
|
网络协议 Java 程序员
完美!腾讯技术官发布Java零基础就业宝典,不用再怀疑人生了
近几年来,互联网行业变化非常大,除了龙头企业的更替,“裁员潮”“失业潮”也不断掀起,尤其是对于年纪太大的程序员真的是不太友好。但是,根据数据统计表明,自2018来,学习IT行业的人不减反增,更有不少其他行业的人转学转行。