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);
    }  

使用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;
          }
      }

  }

先创建了一个线程池,然后再创建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));


    }

在这里插入图片描述

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

缺点

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

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

CompletableFuture

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

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

创建 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();

    }

在这里插入图片描述

例子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());
    }

在这里插入图片描述

可以发现,上述的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;
        });

    }

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

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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值