• JUC并发中Future的使用


    前提

    项目中在处理多并非业务时需要用到多线程,这是我们一般都只是使用一些简单的异步线程来开发我们的需求。

    比如说 new Thread().start,然后传入一个 实现了runable接口的实现类即可,这样可以满足一些简单的并发需求需要 。

    但是这有一些复杂的需求往往需求线程去处理多步流程,比如说我需求拿到线程的执行结果,从而进行我的下一步计算的时候,这时候简单的使用 new Thread() 可满足不了

    这时候就要用到 java.util.concurrent.* 这个java 高性能并发包了,里面提供不了一系列能够满足java多线程复杂场景下的多线程技术。

    FutureTask()

    Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。(异步:可以被叫停,可以被取消)

    ** 一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。**

    在这里插入图片描述
    图中我们可以看到,Future接口主要方法有5个:

    1 cancel(boolean mayInterruptIfRunning) 方法可以用来停止一个任务,如果任务可以停止(通过mayInterruptIfRunning来进行判断),则可以返回true,如果任务已经完成或者已经停止,或者这个任务无法停止,则会返回false

    2 isCancelled( 方法判断当前方法是否取消

    3 get() 方法可以当任务结束后返回一个结果,如果调用时,工作还没有结束,则会阻塞线程,直到任务执行完毕

    4 get(long timeout,TimeUnit unit) 做多等待timeout的时间就会返回结果

    5 isDone() 方法判断当前方法是否完成

    话不多说,直接上代码

    //FutureTask简单使用
        @Test
        public void test0() throws ExecutionException, InterruptedException {
            FutureTask<String>futureTask1=new FutureTask(()->{
               String hello="你好,小北呱";
               return hello;
            });
    
            new Thread(futureTask1).start();
    
            String s = futureTask1.get();
            System.out.println(s);
        }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    使用FutureTask步骤:

    创建 FutureTask()类,然后写入自定义方法,定义好返回值类型,再丢入Thread()中,启动即可

    其中 futureTask1.get() 方式适用获取执行完后的返回值的,这里的返回值是 你好,小北呱,然后再在控制台打印。
    在这里插入图片描述

    不过一般业务量大的使用,我们一般会搭配线程池使用,这样会节省不必要的资源花销:

    //FutureTask配合线程数使用
      @Test
       public void test1() throws ExecutionException, InterruptedException, TimeoutException { 
           ExecutorService executorService= Executors.newFixedThreadPool(3);
           
           FutureTask<String>futureTask1=new FutureTask(() -> {
              Thread.sleep(1000);
              String name=Thread.currentThread().getName();
              return name; 
           });
           FutureTask<String>futureTask2=new FutureTask(() -> {
              Thread.sleep(3000);
              String name=Thread.currentThread().getName();
              return name;
           });
           FutureTask<String>futureTask3=new FutureTask(() -> {
              Thread.sleep(1000);
              String name=Thread.currentThread().getName();
              return name;
           });
          executorService.submit(futureTask1);
          executorService.submit(futureTask2);
          executorService.submit(futureTask3);
          
          List<Future> list=new ArrayList<>();
          list.add(futureTask1);
          list.add(futureTask2);
          list.add(futureTask3);
          
    
          while (true){
              for (int i=0;i<list.size();i++){
                  if (list.get(i).isDone()){
                      System.out.println(list.get(i).get());
                      list.remove(i);
                  }
    
              }
              if (list.size()==0){
                  break;
              }
          }
    
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    先创建了一个线程池,然后再创建3个 FutureTask() 模拟多个并发任务,然后将他们丢入线程池执行即可,再创建一个list集合用于保存他们的执行结果,这里的结果是 Thread.currentThread().getName() ,也就是当前线程的线程名:
    在这里插入图片描述

    优点

    demo:

    //FutureTask demo
        @Test
        public void test0() throws ExecutionException, InterruptedException {
            ExecutorService executorService= Executors.newFixedThreadPool(3);
    
            long start = System.currentTimeMillis();
            FutureTask<String>futureTask1=new FutureTask(()->{
                System.out.println("在准备包子。。。");
                Thread.sleep(2000);
                return "包子准备完毕";
            });
    
            FutureTask<String>futureTask2=new FutureTask(()->{
                System.out.println("在准备凉菜。。。");
                Thread.sleep(2000);
                return "凉菜准备完毕";
            });
    
            FutureTask<String>futureTask3=new FutureTask(()->{
                System.out.println("在准备豆浆。。。");
                Thread.sleep(2000);
                return "凉菜准备完毕";
            });
    
            executorService.submit(futureTask1);
            executorService.submit(futureTask2);
            executorService.submit(futureTask3);
    
            List<Future> list=new ArrayList<>();
            list.add(futureTask1);
            list.add(futureTask2);
            list.add(futureTask3);
            while (true){
                for (int i=0;i<list.size();i++){
                    if (list.get(i).isDone()){
                        System.out.println(list.get(i).get());
                        list.remove(i);
                    }
    
                }
                if (list.size()==0){
                    break;
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("准备完毕时间:"+(end-start));
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    在这里插入图片描述

    耗时我们可以看出,本来要6秒要做到的事情,2秒就结束了,而且还带回来了执行的结果,这就是 FutureTask 类比普通线程的优势

    缺点

    get()阻塞:
    一旦调用get()方法,不管方法在另外执行什么,都会导致阻塞,导致方法停滞不前(所以一般get方法放到最后)

    isDone()轮询:
    利用if(futureTask.isDone())的方式使得他在结束之后才get(),但是也会消耗cpu,过度使用会消耗大量cpu资源

    CompletableFuture

    由于阻塞的方式和异步编程的设计理念相违背,而轮询的方式会消耗无畏的CPU资源。因此,JDK8设计出 CompletableFuture,它相对与futureTask来说更加智能,更加便捷。
    好处:

                1:解决异步线程阻塞取值从而影响执行效率的难题
                2:解决以往轮询方式的获取值导致的CPU消耗问题
                2:使用难度较以往方式降低,效率有所提升  
    
    • 1
    • 2
    • 3

    使用方式

    创建 CompletableFuture 类官方不推荐直接使用 **new()**的方式,因为它只有一个无参构造,这样构造出来的类功能是不完整的,为此推荐使用 它自带的4个静态方法进行类的创建,从而得到不同功能的 CompletableFuture

    1、runAsync(Runnable runnable) 使用默认线程池创建(不带返回值)
    2、runAsync(Runnable runnable,Executor executor) 使用自定义线程池创建(不带返回值)
    ——————————————————————————————————————————————
    3、supplyAsync(Supplier supplier) 使用默认线程池创建(带返回值)
    4、supplyAsync(Supplier supplier,Executor executor) 使用自定义线程池创建(带返回值)

    例子1:

    //不带返回值
    @Test
        public void test1() throws ExecutionException, InterruptedException {
    
            ExecutorService executorService= Executors.newFixedThreadPool(2);
    
            //无返回值方法
            CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(()->{
                System.out.println(Thread.currentThread().getName());
                System.out.println("hello");
            },executorService);
            voidCompletableFuture.get();
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    例子2:

     @Test
        public void test1() throws ExecutionException, InterruptedException {
    
            ExecutorService executorService= Executors.newFixedThreadPool(2);
    
            //带返回值方法
            CompletableFuture<String> voidCompletableFuture = CompletableFuture.supplyAsync(()->{
                System.out.println(Thread.currentThread().getName());
                String s="你好";
                return s;
            },executorService);
            
            System.out.println(voidCompletableFuture.get());
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    可以发现,上述的4个方法中基本可以完成前面提及的FutureTask的所有功能,但是这远远不够,开头我们聊到,我们需要用到多线程中上一步的返回结果用来进行下一步的计算时,就连FutureTask 也得吃闭门羹,因为使用上述的两种方法看来,我们还是需要去阻塞获取结果再进行下一步计算,这样是不明智的,所以请继续看demo:

    @Test
        public void test2()  {
            ExecutorService executorService= Executors.newFixedThreadPool(2);
            
            CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
                String s="大家好";
                return s;
            },executorService).whenComplete((v,e)->{
                //判断是否出现异常
                if (e==null){
                    String x="我是小北呱";
                    String name=v+x;
                    System.out.println(name);
                }
            }).exceptionally((e)->{
                e.printStackTrace();
                System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
                return null;
            });
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    在这里插入图片描述
    重上述代码可以看出,在获取前一步值 (“大家好”) 时,我们不再使用get() 进行阻塞等待了,而是引入了whenComplete() 方法,它的作用就是当上一步执行完后,将结果和值传递给方法内的下一步进行进一步处理。这样就成功的避免了FutureTask类的不良问题,简直是完美

    当然了CompletableFuture 中还新加入的很多方法,这只是个demo,后续会保持更新。。。。。

  • 相关阅读:
    Qt编程,文件操作、UDP通信
    Nacos在Windows本地安装并启动教程
    炸机不可怕,可怕的是你不知道为什么炸
    dubbo以xml方式操作和新版dubbo-admin安装
    UE5修改导航网格的参数
    切糕 小白月赛45
    2022.08.18 第三组 高小涵
    Gateway--服务网关限流
    react+canvas实现刮刮乐效果
    贪心算法实例(一):多任务分配问题
  • 原文地址:https://blog.csdn.net/weixin_43260996/article/details/125910878