异步编程之Future那堆事

有三种 submit。这三种按照提交任务的类型来算分为两个类型。

  • 提交执行 Runnable 类型的任务

  • 提交执行 Callable 类型的任务

他们都是返回Future类型 

看一下 Callable 类型的任务是怎么回事;

异步编程之Future那堆事_第1张图片

 注意这个方法,一会会对比到;

接下来再说说 submit 的任务为 Runable 类型的情况

 ① 的方法扔进去一个 Runable 的任务,返回一个 Future,而这个返回的 Future ,相当于是返回了一个null

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
        Future future = executor.submit(() -> {
            System.out.println("=============");
        });
        System.out.println("future的内容:" + future.get());
        Thread.currentThread().join();


//    future.get() 方法的返回值为 null

  future.get() 方法的返回值为 null

 

标号为 ② 的方法扔进去一个 Runable 的任务的同时,再扔进去一个泛型 T ,而巧好返回的 Future 里面的泛型也是 T,那么我们大胆的猜测一下这就是同一个对象。如果是同一个对象,说明我们可以一个对象传到任务体里面去一顿操作,然后通过 Future 再次拿到这个对象的

ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(10));
        AtomicInteger atomicInteger = new AtomicInteger();
        Future future = executor.submit(() -> {
            System.out.println("===========");
            //在这里进行计算逻辑
            atomicInteger.set(123456);
        }, atomicInteger);

        System.out.println("future的内容:" + future.get());
        Thread.currentThread().join();

// future.get() 结果 123456

 综上,线程池的提交方式一共有四种:一种 execute,无返回值。三种 submit,有返回值。

submit 中按照提交任务的类型又分为两种:一个是 Callable,一个是 Runable。

submit 中 Runable 的任务类型又有两个重载方法:一个返回了null,一个返回了个对象。

也可以说只有 execute 这一种提交方式

最后还有几种很冷门的提交:

从上面的代码我们可以看出,当我们想要返回值的时候,都需要调用下面的这个 get() 方法:

 而从这个方法的描述可以看出,这是一个阻塞方法。拿不到值就在那里等着。当然,还有一个带超时时间的 get 方法,等指定时间后就不等了

总之就是有可能要等的。只要等,那么就是阻塞。只要是阻塞,就是一个假异步。

所以总结一下这种场景下返回的 Future 的不足之处:

  • 只有主动调用 get 方法去获取值,但是有可能值还没准备好,就阻塞等待。

  • 任务处理过程中出现异常会把异常隐藏,封装到 Future 里面去,只有调用 get 方法的时候才知道异常了。

或者你采用带超时时间的 future.get(timeout,unit) 方法

接下来,Guava 的 Future登场:

Guava 的 Future

 新增了一个 addListenter 方法,入参是一个 Runnable 的任务类型和一个线程池

 ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
        ListenableFuture listenableFuture = executor.submit(() -> {
            System.out.println(Thread.currentThread().getName()+"-准备好,叫我");
            TimeUnit.SECONDS.sleep(5);
            return "任务完成了。";
        });

        listenableFuture.addListener(() -> {
            try {
                System.out.println(Thread.currentThread().getName()+"-future的内容:" + listenableFuture.get());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, executor);
        System.out.println(Thread.currentThread().getName()+"-等任务结束之前,我可以干点别的");
        Thread.currentThread().join();

首先创建线程池的方式变了,需要用 Guava 里面的 MoreExecutors 方法装饰一下

ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

然后用装饰后的 executor 调用 submit 方法(任意一种),就会返回 ListenableFuture ,拿到这个 ListenableFuture 之后,我们就可以在上面注册监听:

 所以,上面的程序我们调用的是入参为 callable 类型的接口

获取运行结果是在另外的线程里面执行的,完全没有阻塞主线程

除了上面的 addListener 方法外,另外还有 FutureCallback 的方式

ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
        ListenableFuture listenableFuture = executor.submit(() -> {
            System.out.println(Thread.currentThread().getName()+"-等任务完成叫我");
            TimeUnit.SECONDS.sleep(5);
 // throw new Exception("男神约我看电影,就不和你吃饭了。");
            return "完成了";
        });
        Futures.addCallback(listenableFuture, new FutureCallback() {
            @Override
            public void onSuccess(@Nullable String result) {
                System.out.println(Thread.currentThread().getName()+"-future的内容:" + result);
            }

            @Override
            public void onFailure(Throwable t) {
                System.out.println(Thread.currentThread().getName()+"-有情况,没完成");
                t.printStackTrace();
            }
        });
        System.out.println(Thread.currentThread().getName()+"-等你的时候,干点别的");
        Thread.currentThread().join();

 最后华丽登场的:

加强版的Future - CompletableFuture

第一小节讲的 Future 是 JDK 1.5 时代的产物:

经过了这么多年的发展,Doug Lea 在 JDK 1.8 里面引入了新的 CompletableFuture : 

 

到了 JDK 1.8 时代,这才是真正的异步编程。

CompletableFuture 实现了两个接口,一个是我们熟悉的 Future ,一个是 CompletionStage。

CompletionStage接口,你看这个接口的名称中有一个 Stage :

异步编程之Future那堆事_第2张图片

可以把这个接口理解为一个任务的某个阶段。所以多个 CompletionStage 链接在一起就是一个任务链。前一个任务完成后,下一个任务就会自动触发。

CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "-等任务完成了,叫我");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "任务完成了";
        });

        completableFuture.whenComplete((returnStr, exception) -> {
            if (exception == null) {
                System.out.println(Thread.currentThread().getName() + returnStr);
            } else {
                System.out.println(Thread.currentThread().getName() + "有情况,没完成");
                exception.printStackTrace();
            }
        });
        System.out.println(Thread.currentThread().getName() + "-等你的时候,干点别的");
        Thread.currentThread().join();

 

我们执行的时候并没有指定用什么线程池,但是从结果可以看到也是异步的执行。

从输出日志中是可以看出端倪的,ForkJoinPool.commonPool() 是其默认使用的线程池。

 也可以自己指定

这个方法在很多开源框架里面使用的还是非常的多的。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下来主要看看 CompletableFuture 对于异常的处理。

不需要 try-catch 代码块包裹,也不需要调用 Future.get() 才知道异常了,它提供了一个 handle 方法,可以处理上游异步任务中出现的异常:

CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "-任务完成好了我叫你。");
            throw new RuntimeException("有情况,任务没完成,我告诉你一声。");
        }).handleAsync((result, exception) -> {
            if (exception != null) {
                System.out.println(Thread.currentThread().getName() + "-有情况,没完成");
                return exception.getCause();
            } else {
                return result;
            }
        }).thenApplyAsync((returnStr) -> {
            System.out.println(Thread.currentThread().getName() + "-" + returnStr);
            return returnStr;
        });
        System.out.println(Thread.currentThread().getName() + "-等干完的时候,忙点别的");
        Thread.currentThread().join();

 

你可能感兴趣的:(JUC)