• java 并发编程 Runnable、Callable、FutureTask、Completable


    出处: 

     

    同步计算与异步计算

    从多个任务的角度来看,任务是可以串行执行的,也可以是并发执行的。从单个任务的角度来看,任务的执行方式可以是同步的,也可以是异步的。


    Runnable、Callable、FutureTask

    1、Runnable

    先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法:

    public interface Runnable {
        public abstract void run();
    }

    由于run()方法返回值为void类型,所以在执行完任务之后无法返回任何结果。

    2、Callable

    Callable位于java.util.concurrent包下,它也是一个接口,在它里面也只声明了一个方法,只不过这个方法叫做call():

    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }

    可以看到,这是一个泛型接口,call()函数返回的类型就是传递进来的V类型。

    那么怎么使用Callable呢?一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本:

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

    3、Future

    Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

    Future类位于java.util.concurrent包下,它是一个接口:

    public interface Future<V> {
        boolean cancel(boolean mayInterruptIfRunning);
        boolean isCancelled();
        boolean isDone();
        V get() throws InterruptedException, ExecutionException;
        V get(long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException;
    }

    在Future接口中声明了5个方法,下面依次解释每个方法的作用:

    • cancel 方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true。
    • isCancelled 方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true。
    • isDone 方法表示任务是否已经完成,若任务完成,则返回true;
    • get() 方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
    • get(long timeout, TimeUnit unit) 用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。

      也就是说Future提供了三种功能:

    • 1)判断任务是否完成;
    • 2)能够中断任务;
    • 3)能够获取任务执行结果。

      因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask。

    4、FutureTask

    我们先来看一下FutureTask的实现:

    public class FutureTask<V> implements RunnableFuture<V>

    FutureTask类实现了RunnableFuture接口,我们看一下RunnableFuture接口的实现:

    public interface RunnableFuture<V> extends Runnable, Future<V> {
        void run();
    }

    可以看出RunnableFuture继承了Runnable接口和Future接口,而FutureTask实现了RunnableFuture接口。所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

    FutureTask提供了2个构造器:

    public FutureTask(Callable<V> callable) {
    }
    public FutureTask(Runnable runnable, V result) {
    }

    事实上,FutureTask是Future接口的一个唯一实现类。

    示例:

    public class Test {
        public static void main(String[] args) {
            //第一种方式
            ExecutorService executor = Executors.newCachedThreadPool();
            Task task = new Task();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
            executor.submit(futureTask);
            executor.shutdown();
             
            //第二种方式,注意这种方式和第一种方式效果是类似的,只不过一个使用的是ExecutorService,一个使用的是Thread
            /*Task task = new Task();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
            Thread thread = new Thread(futureTask);
            thread.start();*/
             
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e1) {
                e1.printStackTrace();
            }
             
            System.out.println("主线程在执行任务");
             
            try {
                //futureTask其实是Runnable+Future的综合体,因此可以通过futureTask.get()获取执行结果
                System.out.println("task运行结果"+futureTask.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
             
            System.out.println("所有任务执行完毕");
        }
    }
    class Task implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(3000);
            int sum = 0;
            for(int i=0;i<100;i++)
                sum += i;
            return sum;
        }
    } 

    java Executor框架

    Runnable接口和Callable接口是对任务处理逻辑的抽象,不管是什么样的任务,其处理逻辑总是展现为一个具有统一签名的方法——Runnable.run()或者Callable.call()。二者区别如下:

    1. 方法签名不同,void Runnable.run()V Callable.call() throws Exception
    2. 是否允许有返回值,Callable允许有返回值
    3. 是否允许抛出异常,Callable允许抛出异常。

    java.util.concurrent.Executor接口则是对任务的执行进行的抽象,接口定义了如下方法:

    void execute(Runnable command)

    ExecutorService在Executor的基础上增加了“service”特性的方法:

    • shutdown()shutdownNow(): 都是关闭当前service服务,释放Executor的所有资源(参见实现类);它所触发的动作就是取消队列中任务的执行。shutdown是一种“友好”的关闭,它将不再(事实上是不能)接受新的任务提交,同时把已经提交到队列中的任务执行完毕。shutdownNow更加直接一些,它将会把尚未执行的任务不再执行,正在执行的任务,通过“线程中断”(thread.interrupt),如果线程无法响应“中断”,那么将不会通过此方式被立即结束。shutdowNow是个有返回类型的方法,它返回那些等待执行的任务列表(List<Runnable>)
    • isShutdown: 程序是否已经关闭,1)方法将导致其返回true。
    • isTerminated: 是否已经结束,如果关闭后,所有的任务都执行完成,将返回true,否则其他情况均返回false。
    • awaitTermination(timeout): 会抛出interruptException,此方法就是个废柴,大概意思是等待一段之间直到“任务全部结束”,如果超时就返回false。
    • Future submit(callable/runnale): 向Executor提交任务,并返回一个结果未定的Future。
    • List<Future> invokeAll(Collection<Callable>): 一个废柴方法,同步的方法,执行所有的任务列表,当所有任务都执行完成后,返回Future列表。这方法有啥用??貌似,可以对一批任务进行批量跟踪。此方法会抛出interruptException。
    • T invokeAny(Collection<Callable>):  任务集合中,任何一个任务完成就返回。

    这些方法都会被ExecutorService的子类实现,其实Executor的子类的实现原理,才是最有意义的。其实基于Executor接口自己也能创造世界。

    Executors

    java中提供了Executors工具类,能够返回默认线程工厂、能够将runnable实例转换为callable实例。

    不过阿里开发手册中禁止使用这种方式去创建线程池,需要使用ThreadPoolExecutor,这个后面介绍。

    1、newCachedThreadPool 

    创建一个可缓存线程池,应用中存在的线程数可以无限大,示例代码如下:

    public class Threadpools {
    
        /**
         * 我们获取四次次线程,观察4个线程地址
         * @param args
         */
        public static  void main(String[]args)
        {
            ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
            System.out.println("****************************newCachedThreadPool*******************************");
            for(int i=0;i<4;i++)
            {
                final int index=i;
           //submit本质上还是调用execute(),submit()方法可以有返回结果future
              newCachedThreadPool.submit(new ThreadForpools(index));
            }
        }
    }
    public class ThreadForpools implements Runnable{
    
        private Integer index;
        public  ThreadForpools(Integer index)
        {
         this.index=index;
        }
        @Override
        public void run() {
            /***
             * 业务......省略
              */
            try {
                System.out.println("开始处理线程!!!");
                Thread.sleep(index*100);
                System.out.println("我的线程标识是:" + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    输出结果是:可以有无限大的线程数进来(线程地址不一样)

    2、newFixedThreadPool

    创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

    public class Threadpools {
    
        /**
         * 我们获取四次次线程,观察4个线程地址
         * @param args
         */
        public static  void main(String[]args)
        {
            //线程池允许同时存在两个线程
            ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
            System.out.println("****************************newFixedThreadPool*******************************");
            for(int i=0;i<4;i++)
            {
                final int index=i;
                newFixedThreadPool.submit(new ThreadForpools(index));
            }
        }
    }

    输出结果:每次只有两个线程在处理,当第一个线程执行完毕后,新的线程进来开始处理(线程地址不一样)

     

    3、newScheduledThreadPool

    public class Threadpools {
        /**
         * 我们获取四次次线程,观察4个线程地址
         * @param args
         */
        public static  void main(String[]args)
        {
            ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(2);
            System.out.println("****************************newScheduledThreadPool*******************************");
            for(int i=0;i<4;i++)
            {
                final int index=i;
                //延迟三秒执行
                newScheduledThreadPool.schedule(new ThreadForpools(index), 3, TimeUnit.SECONDS);
            }
        }
    }

    执行结果:延迟三秒之后执行,除了延迟执行之外和newFixedThreadPool基本相同,可以用来执行定时任务

    4、newSingleThreadExecutor

     创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

    public class Threadpools {
        /**
         * 我们获取四次次线程,观察4个线程地址
         * @param args
         */
        public static  void main(String[]args)
        {
            ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
            System.out.println("****************************newSingleThreadExecutor*******************************");
            for(int i=0;i<4;i++)
            {
                final int index=i;
                newSingleThreadExecutor.submit(new ThreadForpools(index));
            }
        }
    }

    执行结果:只存在一个线程,顺序执行

    ThreadPoolExecutor

    Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:

    / Java线程池的完整构造函数
    public ThreadPoolExecutor(
      int corePoolSize, // 线程池长期维持的线程数,即使线程处于Idle状态,也不会回收。
      int maximumPoolSize, // 线程数的上限
      long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的idle时长,超过这个时间,多余的线程会被回收。
      BlockingQueue<Runnable> workQueue, // 任务的排队队列
      ThreadFactory threadFactory, // 新线程的产生方式
      RejectedExecutionHandler handler) // 拒绝策略

    这些参数中,比较容易引起问题的有corePoolSize, maximumPoolSize, workQueue以及handler:

    • corePoolSizemaximumPoolSize设置不当会影响效率,甚至耗尽线程;
    • workQueue设置不当容易导致OOM;
    • handler设置不当会导致提交任务时抛出异常。

    1、线程池的工作顺序

    corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略

    3、如何正确使用线程池

    3.1避免使用无界队列

    不要使用Executors.newXXXThreadPool()快捷方法创建线程池,因为这种方式会使用无界的任务队列,为避免OOM,我们应该使用ThreadPoolExecutor的构造方法手动指定队列的最大长度:

    ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
                    0, TimeUnit.SECONDS, 
                    new ArrayBlockingQueue<>(512), // 使用有界队列,避免OOM
                    new ThreadPoolExecutor.DiscardPolicy());

    3.2明确拒绝任务时的行为

    任务队列总有占满的时候,这是再submit()提交新的任务会怎么样呢?RejectedExecutionHandler接口为我们提供了控制方式,接口定义如下:

    public interface RejectedExecutionHandler {
        void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
    }

    线程池给我们提供了几种常见的拒绝策略:

     

      线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionException异常,该异常是非受检异常,很容易忘记捕获。如果不关心任务被拒绝的事件,可以将拒绝策略设置成DiscardPolicy,这样多余的任务会悄悄的被忽略。 

    ExecutorService executorService = new ThreadPoolExecutor(2, 2, 
                    0, TimeUnit.SECONDS, 
                    new ArrayBlockingQueue<>(512), 
                    new ThreadPoolExecutor.DiscardPolicy());// 指定拒绝策略

    3.3获取处理结果和异常

      线程池的处理结果、以及处理过程中的异常都被包装到Future中,并在调用Future.get()方法时获取,执行过程中的异常会被包装成ExecutionExceptionsubmit()方法本身不会传递结果和任务执行过程中的异常。获取执行结果的代码可以这样写:

    ExecutorService executorService = Executors.newFixedThreadPool(4);
    Future<Object> future = executorService.submit(new Callable<Object>() {
            @Override
            public Object call() throws Exception {
                throw new RuntimeException("exception in call~");// 该异常会在调用Future.get()时传递给调用者
            }
        });
         
    try {
      Object result = future.get();
    } catch (InterruptedException e) {
      // interrupt
    } catch (ExecutionException e) {
      // exception in Callable.call()
      e.printStackTrace();
    }

    上述代码输出类似如下:

    Java8里面CompletableFuture异步编程

    Java8主要的语言增强的能力有:

    (1)lambda表达式

    (2)stream式操作

    (3)CompletableFuture

    什么是CompletableFuture?

      CompletableFuture在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过 回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。CompletableFuture实现了Future, CompletionStage接口,实现了Future接口就可以兼容现在有线程池框架,而CompletionStage接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的CompletableFuture类。

    Future vs CompletableFuture

      Futrue在Java里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个Futrue,在Future里面有isDone方法来 判断任务是否处理结束,还有get方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。

    Future的主要缺点如下:

    (1)不支持手动完成

      这个意思指的是,我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果,通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成。

    (2)不支持进一步的非阻塞调用

      这个指的是我们通过Future的get方法会一直阻塞到任务完成,但是我还想在获取任务之后,执行额外的任务,因为Future不支持回调函数,所以无法实现这个功能。

    (3)不支持链式调用

      这个指的是对于Future的执行结果,我们想继续传到下一个Future处理使用,从而形成一个链式的pipline调用,这在Future中是没法实现的。

    (4)不支持多个Future合并

      比如我们有10个Future并行执行,我们想在所有的Future运行完毕之后,执行某些函数,是没法通过Future实现的。

    (5)不支持异常处理

      Future的API没有任何的异常处理的api,所以在异步运行时,如果出了问题是不好定位的。


    (1)CompletableFuture的静态工厂方法

     runAsync  和 supplyAsync 方法的区别是:

      runAsync返回的CompletableFuture是没有返回值的。

      而supplyAsync返回的CompletableFuture是由返回值的

    CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
                System.out.println("Hello");
            });
    
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            System.out.println("CompletableFuture");
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
    
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
            System.out.println("CompletableFuture");
    (2)Completable

     future.get()在等待执行结果时,程序会一直block,如果此时调用complete(T t)会立即执行。

    CompletableFuture<String> future  = CompletableFuture.supplyAsync(() -> "Hello");
    
            future.complete("World");
    
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

    执行结果:

    World

    可以看到future调用complete(T t)会立即执行。但是complete(T t)只能调用一次,后续的重复调用会失效。

    如果future已经执行完毕能够返回结果,此时再调用complete(T t)则会无效。

    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
    
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            future.complete("World");
    
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

    如果使用completeExceptionally(Throwable ex)则抛出一个异常,而不是一个成功的结果。
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello");
    
            future.completeExceptionally(new Exception());
    
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

    执行结果:

    java.util.concurrent.ExecutionException: java.lang.Exception
    ...

    高级的使用CompletableFuture

      前面提到的几种使用方法是使用异步编程最简单的步骤,CompletableFuture.get()的方法会阻塞直到任务完成,这其实还是同步的概念,这对于一个异步系统是不够的,因为真正的异步是需要支持回调函数,这样以来,我们就可以直接在某个任务干完之后,接着执行回调里面的函数,从而做到真正的异步概念。

    在CompletableFuture里面,我们通过thenApply(), thenAccept(),thenRun()方法,来运行一个回调函数。

    (1)thenApply()

    这个方法,其实用过函数式编程的人非常容易理解,类似于scala和spark的map算子,通过这个方法可以进行多次链式转化并返回最终的加工结果。

    看下面一个例子:

    public static void asyncCallback() throws ExecutionException, InterruptedException {
    
            CompletableFuture<String> task=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println(getThreadName()+"supplyAsync");
                    return "123";
                }
            });
    
            CompletableFuture<Integer> result1 = task.thenApply(number->{
                System.out.println(getThreadName()+"thenApply1");
                return Integer.parseInt(number);
            });
    
            CompletableFuture<Integer> result2 = result1.thenApply(number->{
                System.out.println(getThreadName()+"thenApply2");
                return number*2;
            });
    
            System.out.println(getThreadName()+" => "+result2.get());
    
        }

    输出结果:

    ForkJoinPool.commonPool-worker-1线程=> supplyAsync
    main线程=> thenApply1
    main线程=> thenApply2
    main线程=>  => 246

    (2)thenAccept()

    这个方法,可以接受Futrue的一个返回值,但是本身不在返回任何值,适合用于多个callback函数的最后一步操作使用。

    例子如下:

    public static void asyncCallback2() throws ExecutionException, InterruptedException {
            CompletableFuture<String> task=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    System.out.println(getThreadName()+"supplyAsync");
                    return "123";
                }
            });
    
            CompletableFuture<Integer> chain1 = task.thenApply(number->{
                System.out.println(getThreadName()+"thenApply1");
                return Integer.parseInt(number);
            });
    
            CompletableFuture<Integer> chain2 = chain1.thenApply(number->{
                System.out.println(getThreadName()+"thenApply2");
                return number*2;
            });
    
           CompletableFuture<Void> result=chain2.thenAccept(product->{
               System.out.println(getThreadName()+"thenAccept="+product);
           });
    
            result.get();
            System.out.println(getThreadName()+"end");
    
    
        }

    结果如下:

    ForkJoinPool.commonPool-worker-1线程=> supplyAsync
    main线程=> thenApply1
    main线程=> thenApply2
    main线程=> thenAccept=246
    main线程=> end

    (3) thenRun()

    这个方法与上一个方法类似,一般也用于回调函数最后的执行,但这个方法不接受回调函数的返回值,纯粹就代表执行任务的最后一个步骤:

    public  static void asyncCallback3() throws ExecutionException, InterruptedException {
            CompletableFuture.supplyAsync(()->{
                System.out.println(getThreadName()+"supplyAsync: 一阶段任务");
                return null;
            }).thenRun(()->{
                System.out.println(getThreadName()+"thenRun: 收尾任务");
            }).get();
        }

    结果:

    ForkJoinPool.commonPool-worker-1线程=> supplyAsync: 一阶段任务
    main线程=> thenRun: 收尾任务

    这里注意,截止到目前,前面的例子代码只会涉及两个线程,一个是主线程一个是ForkJoinPool池的线程,但其实上面的每一步都是支持异步运行的,其api如下:

    // thenApply() variants
    <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
    <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn)
    <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor)

    我们看下改造后的一个例子:

    public  static void asyncCallback4() throws ExecutionException, InterruptedException {
    
            CompletableFuture<String> ref1=  CompletableFuture.supplyAsync(()->{
                try {
                    System.out.println(getThreadName()+"supplyAsync开始执行任务1.... ");
    //                TimeUnit.SECONDS.sleep(1);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(getThreadName()+"supplyAsync: 任务1");
                return null;
            });
    
            CompletableFuture<String> ref2= CompletableFuture.supplyAsync(()->{
                try {
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(getThreadName()+"thenApplyAsync: 任务2");
                return null;
            });
    
            CompletableFuture<String> ref3=ref2.thenApplyAsync(value->{
                System.out.println(getThreadName()+"thenApplyAsync: 任务2的子任务");
                return  null;
            });
    
    
            Thread.sleep(4000);
            System.out.println(getThreadName()+ref3.get());
        }

    输出结果如下:

    ForkJoinPool.commonPool-worker-1线程=> supplyAsync开始执行任务1.... 
    ForkJoinPool.commonPool-worker-1线程=> supplyAsync: 任务1
    ForkJoinPool.commonPool-worker-1线程=> supplyAsync: 任务2
    ForkJoinPool.commonPool-worker-2线程=> thenApplyAsync: 任务2的子任务
    main线程=> null

    我们可以看到,ForkJoin池的线程1,执行了前面的三个任务,但是第二个任务的子任务,因为我们了使用也异步提交所以它用的线程是ForkJoin池的线程2,最终由于main线程处执行了get是最后结束的。

    还有一点需要注意:

    ForkJoinPool所有的工作线程都是守护模式的,也就是说如果主线程退出,那么整个处理任务都会结束,而不管你当前的任务是否执行完。如果需要主线程等待结束,可采用ExecutorsThreadPool,如下:

    ExecutorService pool = Executors.newFixedThreadPool(5);
    final CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
                    ... }, pool);

    (4)thenCompose合并两个有依赖关系的CompletableFutures的执行结果

    CompletableFutures在执行两个依赖的任务合并时,会返回一个嵌套的结果列表,为了避免这种情况我们可以使用thenCompose来返回,直接获取最顶层的结果数据即可:

    public static void asyncCompose() throws ExecutionException, InterruptedException {
    
            CompletableFuture<String>  future1=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    return "1";
                }
            });
    
           CompletableFuture<String>nestedResult = future1.thenCompose(value->
                   CompletableFuture.supplyAsync(()->{
                    return value+"2";
           }));
    
            System.out.println(nestedResult.get());
        }

    (5)thenCombine合并两个没有依赖关系的CompletableFutures任务

    CompletableFuture<Double>  d1= CompletableFuture.supplyAsync(new Supplier<Double>() {
                @Override
                public Double get() {
                    return 1d;
                }
            });
    
            CompletableFuture<Double>  d2= CompletableFuture.supplyAsync(new Supplier<Double>() {
                @Override
                public Double get() {
                    return 2d;
                }
            });
    
    
          CompletableFuture<Double> result=  d1.thenCombine(d2,(number1,number2)->{
                return  number1+number2;
            });
    
    
            System.out.println(result.get());

    (6)合并多个任务的结果allOf与anyOf

    上面说的是两个任务的合并,那么多个任务需要使用allOf或者anyOf方法。

    allOf适用于,你有一系列独立的future任务,你想等其所有的任务执行完后做一些事情。举个例子,比如我想下载100个网页,传统的串行,性能肯定不行,这里我们采用异步模式,同时对100个网页进行下载,当所有的任务下载完成之后,我们想判断每个网页是否包含某个关键词。

    下面我们通过随机数来模拟上面的这个场景如下:

    public static void mutilTaskTest() throws ExecutionException, InterruptedException {
    
             //添加n个任务
            CompletableFuture<Double> array[]=new CompletableFuture[3];
            for ( int i = 0; i < 3; i++) {
                array[i]=CompletableFuture.supplyAsync(new Supplier<Double>() {
                    @Override
                    public Double get() {
                        return Math.random();
                    }
                });
            }
    
           //获取结果的方式一
    //       CompletableFuture.allOf(array).get();
    //        for(CompletableFuture<Double> cf:array){
    //            if(cf.get()>0.6){
    //                System.out.println(cf.get());
    //            }
    //        }
            //获取结果的方式二,过滤大于指定数字,在收集输出
           List<Double> rs= Stream.of(array).map(CompletableFuture::join).filter(number->number>0.6).collect(Collectors.toList());
           System.out.println(rs);
    
        }

    结果如下:

    [0.8228784717152199]

    注意其中的join方法和get方法类似,仅仅在于在Future不能正常完成的时候抛出一个unchecked的exception,这可以确保它用在Stream的map方法中,直接使用get是没法在map里面运行的。

    anyOf方法,也比较简单,意思就是只要在多个future里面有一个返回,整个任务就可以结束,而不需要等到每一个future结束。

    CompletableFuture<String> f1=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    try {
                        TimeUnit.SECONDS.sleep(4);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "wait 4 seconds";
                }
            });
    
            CompletableFuture<String> f2=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "wait 2 seconds";
                }
            });
    
    
            CompletableFuture<String> f3=CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    try {
                        TimeUnit.SECONDS.sleep(4);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    return "wait 10 seconds";
                }
            });
    
    
    
           CompletableFuture<Object> result= CompletableFuture.anyOf(f1,f2,f3);
    
    
            System.out.println(result.get());

    输出结果:

    wait 2 seconds

    注意由于Anyof返回的是其中任意一个Future所以这里没有明确的返回类型,统一使用Object接受,留给使用端处理。

    (7)exceptionally异常处理

    异常处理是异步计算的一个重要环节,下面看看如何在CompletableFuture中使用:

    int age=-1;
           CompletableFuture<String> task= CompletableFuture.supplyAsync(new Supplier<String>() {
               @Override
               public String get() {
    
                   if(age<0){
                       throw new IllegalArgumentException("性别必须大于0");
                   }
    
                   if(age<18){
                       return "未成年人";
                   }
    
                   return "成年人";
               }
           }).exceptionally(ex->{
               System.out.println(ex.getMessage());
               return "发生 异常"+ex.getMessage();
           });
    
    
            System.out.println(task.get());

    结果如下:

    java.lang.IllegalArgumentException: 性别必须大于0
    发生 异常java.lang.IllegalArgumentException: 性别必须大于0

    此外还有另外一种异常捕捉方法handle,无论发生异常都会执行,示例如下:

    int age=10;
            CompletableFuture<String> task= CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
    
                    if(age<0){
                        throw new IllegalArgumentException("性别必须大于0");
                    }
    
                    if(age<18){
                        return "未成年人";
                    }
    
                    return "成年人";
                }
            }).handle((res,ex)->{
                System.out.println("执行handle");
                if(ex!=null){
                    System.out.println("发生异常");
                    return "发生 异常"+ex.getMessage();
                }
    
                return res;
            });
    
    
            System.out.println(task.get());

    输出结果:

    执行handle
    发生异常
    发生 异常java.lang.IllegalArgumentException: 性别必须大于0

    注意上面的方法如果正常执行,也会执行handle方法。

    JDK9 CompletableFuture 类增强的主要内容

    (1)支持对异步方法的超时调用

    orTimeout()
    completeOnTimeout()

    (2)支持延迟调用

    Executor delayedExecutor(long delay, TimeUnit unit, Executor executor)
    Executor delayedExecutor(long delay, TimeUnit unit)

    详细内容,可以参考Oracle官网文档,这里不再过多介绍。

    总结:

      本文主要介绍了CompletableFuture的定义,概念及在Java中使用的例子,通过CompletableFuture我们可以实现异步编程的能力,从而使得我们开发的任务可以拥有更强大的能力。

  • 相关阅读:
    [管理]管理者应需要知道的一些定律
    推荐IOS Moneky测试工具Fast Monkey
    [原创]强烈推荐Cmd终极替换工具Cmder
    [原创] 2018年测试行业各职位薪水参考表
    [原创]管理者如何激励下属更有效的工作?
    [原创]SonarQube代码质量检查工具介绍
    [原创]F2etest是一个面向前端、测试、产品等岗位的多浏览器兼容性测试整体解决方案。
    [原创] IMB AppScan 9.0下载及破解
    [原创] Influxdb+Grafana+Jmeter性能监控工具搭建
    [原创]浅谈互联网金融接口测试平台搭建
  • 原文地址:https://www.cnblogs.com/myseries/p/12481844.html
Copyright © 2020-2023  润新知