CompletableFuture的简单用法

Java8中的CompletableFuture是对Future的扩展实现, 主要是为了弥补Future没有相应的回调机制的缺陷。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T>

CompletableFuture实现了Future和CompletionStage两个接口。

先看看 Java8 之前的 Future 的使用:

package demos;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService cachePool = Executors.newCachedThreadPool();
        Future<String> future = cachePool.submit(() -> {
            Thread.sleep(3000);
            return "异步任务计算结果!";
        });
        // 提交完异步任务后, 主线程可以继续干一些其他的事情.
        doSomeThingElse();
        // 为了获取异步计算结果, 我们可以通过 future.get 和 轮询机制来获取.
        String result;
        // Get 方式会导致当前线程阻塞, 这显然违背了异步计算的初衷.
        // result = future.get();
        // 轮询方式虽然不会导致当前线程阻塞, 但是会导致高额的 CPU 负载.
        long start = System.currentTimeMillis();
        while (true) {
            if (future.isDone()) {
                break;
            }
        }
        System.out.println("轮询耗时:" + (System.currentTimeMillis() - start));
        result = future.get();
        System.out.println("获取到异步计算结果啦: " + result);
        cachePool.shutdown();
    }

    private static void doSomeThingElse() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("重要的事情干完了,获取异步计算结果来执行剩下的事情.");
    }
}

输出结果:

重要的事情干完了, 获取异步计算结果来执行剩下的事情.
轮询耗时:2000
获取到异步计算结果啦: 异步任务计算结果!

从上面的 Demo 中我们可以看出, future 在执行异步任务时, 对于结果的获取显的不那么优雅, 很多第三方库就针对 Future 提供了回调式的接口以用来获取异步计算结果, 如Google的: ListenableFuture, 而 Java8 所提供的 CompletableFuture 便是官方为了弥补这方面的不足而提供的 API。

简单介绍CompletableFuture的用法:

package demos;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CompleteFutureDemo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> completableFutureOne = new CompletableFuture<>();

        ExecutorService cachePool = Executors.newCachedThreadPool();
        cachePool.execute(() -> {
            try {
                Thread.sleep(3000);
                completableFutureOne.complete("异步任务执行结果");
                System.out.println(Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // WhenComplete 方法返回的 CompletableFuture 仍然是原来的 CompletableFuture 计算结果
        CompletableFuture<String> completableFutureTwo = completableFutureOne.whenComplete((s, throwable) -> {
            System.out.println("当异步任务执行完毕时打印异步任务的执行结果: " + s);
        });
        // ThenApply 方法返回的是一个新的 completeFuture.
        CompletableFuture<Integer> completableFutureThree = completableFutureTwo.thenApply(s -> {
            System.out.println("当异步任务执行结束时, 根据上一次的异步任务结果, 继续开始一个新的异步任务!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return s.length();
        });
        System.out.println("阻塞方式获取执行结果:" + completableFutureThree.get());
        cachePool.shutdown();
    }
}

1、runAsync/supplyAsync-创建CompletableFuture对象

一个CompletableFuture对象代表着一个任务

创建CompletableFuture对象:

public static <U> CompletableFuture<U> completedFuture(U value)

CompletableFuture.completedFuture是一个静态辅助方法,用来返回一个已经计算好的CompletableFuture。

而以下四个静态方法用来为一段异步执行的代码创建CompletableFuture对象:

public static CompletableFuture<Void> 	runAsync(Runnable runnable)
public static CompletableFuture<Void> 	runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> 	supplyAsync(Supplier<U> supplier, Executor executor)

以Async结尾并且没有指定Executor的方法会使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。runAsync方法也好理解,它以Runnable函数式接口类型为参数,所以CompletableFuture的计算结果为空。supplyAsync方法以Supplier<U>函数式接口类型为参数,CompletableFuture的计算结果类型为U。

CompletableFuture中的很多静态方法主要可以分成两类,方法后缀带不带Async,不带Async后缀的静态方法表示执行该方法的是当前线程,带Async后缀的静态方法表示使用其他线程异步执行,如果方法参数中指定了线程池,则使用指定线程池去执行任务,如果方法参数中没有指定线程池,则默认使用ForkJoinPool.commonPool()作为它的线程池执行异步代码。

2、whenComplete

public CompletableFuture<T>	whenComplete(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action)
public CompletableFuture<T>	whenCompleteAsync(BiConsumer<? super T,? super Throwable> action, Executor executor)

whenComplete方法是任务执行完毕时调用的,传入一个action。这个方法的执行线程是当前线程,意味着会阻塞当前线程。

看两个例子:

package com.multi.thread.completableFuture.study;

import java.util.concurrent.CompletableFuture;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

public class WhenCompleteMethodStudy {
    public static void main(String[] args) {
        CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("Start creating CompletableFuture.");
            sleep(100);
            System.out.println("The thread of creating CompletableFuture is: " + Thread.currentThread());
            return "成功创建CompletableFuture。";
        });
        System.out.println("===111-main线程执行操作===" + Thread.currentThread());
//        sleep(500);
        System.out.println("===222-main线程继续操作===" + Thread.currentThread());
        completableFuture.whenComplete((o1, o2) -> {
            System.out.println("The thread of call whenComplete method is: " + Thread.currentThread());
            sleep(200);
            System.out.println("输出结果:" + o1);
        });
        System.out.println("===main线程休息===" + Thread.currentThread());
//        sleep(1000);
        System.out.println("===main线程结束===" + Thread.currentThread());
    }

    private static void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如上图所示,如果主线程很快执行结束了,可能会使得要执行CompletableFuture.supplyAsync方法中任务的线程还未来得及执行。

如果在执行CompletableFuture.supplyAsync任务与执行completableFuture.whenComplete任务之间没有耗时的操作,如上图所示,主线程都快执行结束了,执行执行CompletableFuture.supplyAsync任务的线程还未结束,等到该任务执行结束,就会使用这个线程继续执行completableFuture.whenComplete任务,不是主线程执行completableFuture.whenComplete任务任务。

根据测试得出的结论是:如果调用whenComplete的中途,还发生了其他事情,如图中主线程的sleep(500),导致completableFuture这个任务执行完毕了,主线程还未执行到whenComplete方法,那么就使用主线程调用。如果主线程在中途没有发生耗时操作,且在触碰到whenComplete方法时completableFuture这个任务还没有彻底执行完毕,那么就会用completableFuture这个任务所使用的线程去执行whenComplete。

whenCompleteAsync方法是新创建一个异步线程执行任务,所以不会阻塞主线程。

package com.study;

import java.util.Random;
import java.util.concurrent.CompletableFuture;

public class WhenCompleteDemo {

    private static Random rand = new Random();
    private static long t = System.currentTimeMillis();
    static int getMoreData() {
        System.out.println("begin to start compute");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("end to start compute. passed " + (System.currentTimeMillis() - t)/1000 + " seconds");
        return rand.nextInt(1000);
    }

    public static void main(String[] args) throws Exception {
        CompletableFuture<Integer> futureOne = CompletableFuture.supplyAsync(WhenCompleteDemo::getMoreData);
        //whenComplete方法会得到一个CompletableFuture对象,
        //该对象的结果就是调用whenComplete方法的CompletableFuture对象的结果,
        //即futureTwo的结果就是futureOne的结果
        CompletableFuture<Integer> futureTwo = futureOne.whenComplete((v, e) -> {
            System.out.println("futureOne的结果:" + v);
            System.out.println("futureOne的异常:" + e);
        });
        System.out.println("主线程阻塞等待获取执行结果");
        //futureTwo的结果就是futureOne的结果
        System.out.println("阻塞获取futureTwo的结果:" + futureTwo.get());
    }

}

3、handle/thenApply

下面一组方法虽然也返回CompletableFuture对象,但是新的CompletableFuture对象的值和原来的CompletableFuture计算的值不同。当原先的CompletableFuture的值计算完成或者抛出异常的时候,会触发新的CompletableFuture对象的计算,结果由BiFunction参数计算而得。因此这组方法兼有whenComplete和转换的两个功能。

public <U> CompletableFuture<U> 	handle(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> 	handleAsync(BiFunction<? super T,Throwable,? extends U> fn)
public <U> CompletableFuture<U> 	handleAsync(BiFunction<? super T,Throwable,? extends U> fn, Executor executor)

同样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。

下面一组方法与上面的一组handle方法类似,都是根据原来的CompletableFuture对象生成新的CompletableFuture对象。

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

这一组函数的功能是当原来的CompletableFuture计算完后,将结果传递给函数fn,将fn的结果作为新的CompletableFuture计算结果。因此它的功能相当于将CompletableFuture<T>转换成CompletableFuture<U>。

这三个函数的区别和上面介绍的一样,不以Async结尾的方法由原来的线程计算,以Async结尾的方法由默认的线程池ForkJoinPool.commonPool()或者指定的线程池executor运行。

public static void thenApplyTest(){
    CompletableFuture<Integer> future=CompletableFuture.supplyAsync(()->{
        return 100;
    });
    CompletableFuture<String> f=future.thenApplyAsync(i->i*10).thenApply(i->i.toString());
    try{
        System.out.println("thenApply测试结果:"+f.get());
    }catch(InterruptedException|ExecutionException e){
        e.printStackTrace();
    }
}

输出结果:

thenApply测试结果:1000

需要注意的是,这些转换并不是马上执行的,也不会阻塞,而是在前一个stage完成后继续执行。

它们与handle方法的区别在于handle方法会处理正常计算值和异常,因此它可以屏蔽异常,避免异常继续抛出。而thenApply方法只是用来处理正常值,因此一旦有异常就会抛出。

4、thenAccept

上面的方法是当计算完成的时候,会生成新的计算结果(thenApply, handle),或者返回同样的计算结果whenComplete,CompletableFuture还提供了一种处理结果的方法,只对结果执行Action,而不返回新的计算值,因此计算值为Void。

public CompletableFuture<Void> 	thenAccept(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action)
public CompletableFuture<Void> 	thenAcceptAsync(Consumer<? super T> action, Executor executor)

看它的参数类型也就明白了,它们是函数式接口Consumer,这个接口只有输入,没有返回值。

public static void thenAcceptTest(){
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<Void> f =  future.thenAccept(i ->
            System.out.println("supplyAsync方法生成的CompletableFuture的结果:" + i));
    try {
        System.out.println("thenAccept方法生成的CompletableFuture的结果:" + f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

输出结果:

supplyAsync方法生成的CompletableFuture的结果:100

thenAccept方法生成的CompletableFuture的结果:null

5、thenAcceptBoth

public <U> CompletableFuture<Void> 	thenAcceptBoth(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> 	thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action)
public <U> CompletableFuture<Void> 	thenAcceptBothAsync(CompletionStage<? extends U> other, BiConsumer<? super T,? super U> action, Executor executor)
public     CompletableFuture<Void> 	runAfterBoth(CompletionStage<?> other,  Runnable action)

thenAcceptBoth方法表示当两个CompletionStage都正常完成计算的时候,就会执行提供的action,它用来组合另外一个异步的结果。此处的两个CompletionStage是指:一个是调用thenAcceptBoth方法的CompletionStage,另一个是thenAcceptBoth方法参数中的CompletionStage。CompletableFuture是CompletionStage接口的实现类。

runAfterBoth是当两个CompletionStage都正常完成计算的时候,执行一个Runnable,这个Runnable并不使用计算的结果。

public static void thenAcceptBothTest(){
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<Void> f =  future.thenAcceptBoth(
            CompletableFuture.completedFuture(10), (x, y)
                    -> System.out.println("将两个CompletableFuture的结果组合:" + x * y));
    try {
        System.out.println("thenAcceptBoth方法生成的CompletableFuture的结果:" + f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

输出结果:

将两个CompletableFuture的结果组合:1000

thenAcceptBoth方法生成的CompletableFuture的结果:null

6、thenRun

更彻底地,下面一组方法当计算完成的时候会执行一个Runnable,与thenAccept不同,Runnable并不使用CompletableFuture计算的结果,Runnable接口的run方法既没有参数也没有返回值。因此先前的CompletableFuture计算的结果被忽略了,这个方法返回CompletableFuture<Void>类型的对象。

public CompletableFuture<Void> 	thenRun(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action)
public CompletableFuture<Void> 	thenRunAsync(Runnable action, Executor executor)
public static void thenRunTest(){
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<Void> f =  future.thenRun(() ->
            System.out.println("原来的CompletableFuture的结果被丢弃了"));
    try {
        System.out.println("thenRun方法生成的CompletableFuture的结果:" + f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

输出结果:

原来的CompletableFuture的结果被丢弃了

thenRun方法生成的CompletableFuture的结果:null

因此,可以根据方法参数的类型来加速记忆:Runnable类型的参数会忽略计算的结果,Consumer是纯消费计算结果,BiConsumer会组合另外一个CompletionStage纯消费,Function会对计算结果做转换,BiFunction会组合另外一个CompletionStage的计算结果做转换。

7、thenCompose/thenCombine

public <U> CompletableFuture<U> 	thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> 	thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn)
public <U> CompletableFuture<U> 	thenComposeAsync(Function<? super T,? extends CompletionStage<U>> fn, Executor executor)

这一组方法接受一个Function作为参数,这个Function的输入是当前的CompletableFuture的计算值,返回结果将是一个新的CompletableFuture,这个新的CompletableFuture会组合原来的CompletableFuture的结果。

public static void thenComposeTest() {
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    /*CompletableFuture<String> f1 = future.thenCompose(
        new Function<Integer, CompletionStage<String>>() {
            @Override
            public CompletionStage<String> apply(Integer i) {
                return CompletableFuture.supplyAsync(new Supplier<String>() {
                    @Override
                    public String get() {
                        return (i * 10) + "";
                    }
                });
            }
        });*/
    //上面这段代码的lambda表达式的写法如下
    CompletableFuture<String> f = future.thenCompose(i -> {
        return CompletableFuture.supplyAsync(() -> {
            return (i * 10) + "";
        });
    });
    try {
        System.out.println(f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

下面的一组方法thenCombine也是用来复合另外一个CompletionStage的结果。两个CompletionStage是并行执行的,它们之间并没有先后依赖顺序,other并不会等待先前的CompletableFuture执行完毕后再执行。其实从功能上来讲,它们的功能更类似thenAcceptBoth,只不过thenAcceptBoth是纯消费,它的函数参数没有返回值,而thenCombine的函数参数fn有返回值。

public <U,V> CompletableFuture<V> 	thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> 	thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn)
public <U,V> CompletableFuture<V> 	thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn, Executor executor)
public static void thenCombineTest(){
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        return 100;
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        return "abc";
    });
    CompletableFuture<String> f =  future.thenCombine(future2, (x,y) -> y + "-" + x);
    /*CompletableFuture<String> f1 = future.thenCombine(future2,
        new BiFunction<Integer, String, String>() {
        //第一个参数是future的结果,第二个参数是future2的结果
        @Override
        public String apply(Integer x, String y) {
            return y + "-" + x;
        }
    });*/
    try {
        System.out.println("thenCombine方法生成的CompletableFuture的结果:" + f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

输出结果:

thenCombine方法生成的CompletableFuture的结果:abc-100

8、acceptEither/applyToEither

thenAcceptBoth和runAfterBoth是当两个CompletableFuture都计算完成,而下面要了解的方法是当任意一个CompletableFuture计算完成的时候就会执行。

public CompletableFuture<Void> 	acceptEither(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> 	acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action)
public CompletableFuture<Void> 	acceptEitherAsync(CompletionStage<? extends T> other, Consumer<? super T> action, Executor executor)
public <U> CompletableFuture<U> 	applyToEither(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> 	applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn)
public <U> CompletableFuture<U> 	applyToEitherAsync(CompletionStage<? extends T> other, Function<? super T,U> fn, Executor executor)

acceptEither方法是当任意一个CompletionStage完成的时候,action这个消费者就会被执行。这个方法返回CompletableFuture<Void>。

applyToEither方法是当任意一个CompletionStage完成的时候,fn会被执行,它的返回值会当作新的CompletableFuture<U>的计算结果。它的返回值是先执行完的那个CompletableFuture的结果经过fn函数转换之后的结果。

下面这个例子有时会输出100,有时候会输出200,哪个Future先完成就会根据它的结果计算。

public static void eitherTest(){
    Random rand = new Random();
    CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000 + rand.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 100;
    });
    CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000 + rand.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 200;
    });
    CompletableFuture<String> f =  future.applyToEither(future2,i -> i.toString());
    try {
        System.out.println(f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

9、allOf/anyOf/join

前面我们已经介绍了几个静态方法:completedFuture、runAsync、supplyAsync,下面介绍的这两个方法用来组合多个CompletableFuture,参数是CompletableFuture数组。

public static CompletableFuture<Void> 	    allOf(CompletableFuture<?>... cfs)
public static CompletableFuture<Object> 	anyOf(CompletableFuture<?>... cfs)

allOf方法是当所有的CompletableFuture都执行完后执行计算。

anyOf方法是当任意一个CompletableFuture执行完后就会执行计算,计算的结果相同。

anyOf和applyToEither不同:anyOf接受任意多的CompletableFuture,但是applyToEither只是判断两个CompletableFuture;anyOf返回值的计算结果是参数中其中一个CompletableFuture的计算结果,applyToEither返回值的计算结果却是要经过fn处理的。

下面的代码运行结果有时是100,有时是"abc"。

public static void anyOfTest(){
    Random rand = new Random();
    CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000 + rand.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 100;
    });
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        try {
            Thread.sleep(3000 + rand.nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "abc";
    });
    //CompletableFuture<Void> f =  CompletableFuture.allOf(future1,future2);
    CompletableFuture<Object> f =  CompletableFuture.anyOf(future1,future2);
    try {
        System.out.println(f.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

如果用过Guava的Future类,你就会知道它的Futures辅助类提供了很多便利方法,用来处理多个Future,而不像Java的CompletableFuture,只提供了allOf、anyOf两个方法。 比如有这样一个需求,将多个CompletableFuture组合成一个CompletableFuture,这个组合后的CompletableFuture的计算结果是个List,它包含前面所有的CompletableFuture的计算结果,guava的Futures.allAsList可以实现这样的功能,但是对于java CompletableFuture,我们需要一些辅助方法。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Function;

public static void allOfTest() { 
    //不要使用HashMap,会存在并发修改异常
    ConcurrentMap<String, Object> map = new ConcurrentHashMap<>();
    List<String> list = new ArrayList<>();
    list.add("zhangsan");
    list.add("lisi");
    list.add("wangwu");
    /*List<CompletableFuture> futureList =
            list.stream().map(new Function<String, CompletableFuture>() {
                @Override
                public CompletableFuture apply(String s) {
                    return CompletableFuture.runAsync(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                System.out.println("异步执行任务,耗时1秒");
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            map.put(s, s.length());
                        }
                    });
                }
            }).collect(Collectors.toList());
    CompletableFuture[] futures = futureList.toArray(new CompletableFuture[0]);*/
    CompletableFuture[] futures = list.stream().map(new Function<String, CompletableFuture>() {
        @Override
        public CompletableFuture apply(String s) {
            return CompletableFuture.runAsync(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        System.out.println("异步执行任务,耗时1秒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
		//在多线程中使用Map时一定要使用ConcurrentHashMap,如果使用List,也要使用同步的List<Object> list = Collections.synchronizedList(new ArrayList<>());
                    map.put(s, s.length());
                }
            });
        }
    }).toArray(CompletableFuture[]::new);
    CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(futures);
    System.out.println("使用join阻塞主线程,与线程的join用法类似");
    allOfFuture.join();
    /*CompletableFuture<Object> future = allOfFuture.thenApply(new Function<Void, Object>() {
        @Override
        public Object apply(Void unused) {
            return futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
        }
    });
    future.join();*/
    System.out.println("阻塞结束之后,主线程继续执行");
    if (map.size() != 3){
        System.out.println("出现错误----------" + map.size());
    }
    List<String> keys = new ArrayList<>(map.keySet());
    for (int i=0;i<keys.size();i++){
        System.out.println(keys.get(i) + "===" + map.get(keys.get(i)) + "===" + i);
    }
    /*for (Map.Entry<String,Object> me : map.entrySet()){
        System.out.println(me.getKey() + "========" + me.getValue());
    }*/
}

得到的结果如下:

使用join阻塞主线程,与线程的join用法类似
异步执行任务,耗时1秒
异步执行任务,耗时1秒
异步执行任务,耗时1秒
阻塞结束之后,主线程继续执行
lisi===4===0
zhangsan===8===1
wangwu===6===2

根据这些结果来看,主线程在join方法处确实被阻塞了。

10、抛出异常

CompletableFuture处理的异常都会封装成java.util.concurrent.CompletionException抛出,会丢失原有异常类信息。

1、pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
    </parent>

    <modelVersion>4.0.0</modelVersion>
    <artifactId>multi-thread</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.3.12.RELEASE</version>
        </dependency>
    </dependencies>

</project>

2、启动类

package com.multi.thread;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MultiThreadApplication {

    public static void main(String[] args) {
        SpringApplication.run(MultiThreadApplication.class,args);
    }

}

3、自定义异常

package com.multi.thread.exception;

public class CustomerException extends RuntimeException {

    private String errCode;

    private String errMsg;

    public CustomerException(String errMsg){
        super(errMsg);
    }

    public CustomerException(String errCode, String errMsg) {
        super(errMsg);
        this.errCode = errCode;
    }

    public String getErrCode() {
        return errCode;
    }

    public String getErrMsg() {
        return errMsg;
    }
}

4、统一响应实体类

package com.multi.thread.resp;

public class RespBody {

    private Integer code;

    private String msg;

    private Object data;

    public RespBody() {
    }

    public RespBody(String msg) {
        this.msg = msg;
    }

    public RespBody(String msg, Object data) {
        this.msg = msg;
        this.data = data;
    }

    public RespBody(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public static RespBody ok(){
        return new RespBody(0,"","");
    }

    public static RespBody ok(String msg){
        return new RespBody(0,msg,"");
    }

    public static RespBody ok(String msg, Object data){
        return new RespBody(0,msg,data);
    }

    public static RespBody fail(String msg){
        return new RespBody(-1,msg,"");
    }

    /*
    * 需要为至少一个属性添加getter方法,否则会报如下错误:
    * No converter found for return value of type: class com.multi.thread.resp.RespBody
    * */
    public Integer getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public Object getData() {
        return data;
    }
}

5、统一异常处理器

package com.multi.thread.handler;

import com.multi.thread.exception.CustomerException;
import com.multi.thread.resp.RespBody;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.CompletionException;

@ControllerAdvice
public class UniqueExceptionHandler {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public RespBody handleException(Throwable e){
        System.out.println("进入到异常处理器中");
        if (e instanceof CustomerException){
            System.out.println("customer exception!!!!!");
            return RespBody.fail(((CustomerException) e).getErrMsg());
        }
        if (e instanceof CompletionException){
            System.out.println("completion exception!!!!!");
            return RespBody.fail(e.getCause().getMessage());
        } else {
            System.out.println("other exception!!!!!");
            return RespBody.fail("other exception");
        }
    }

}

6、service业务类

package com.multi.thread.service;

import com.multi.thread.exception.CustomerException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class CompletableFutureService {

    public void comFutureTest(){
        List<Integer> list = new ArrayList<>();
        for (int i=1;i<=10;i++){
            list.add(i);
        }

        //必须要有返回值,否则异常不会被抛出,就不能进入到异常处理器中
        List<CompletableFuture<Void>> futures = list.stream().map(
                new Function<Integer, CompletableFuture<Void>>() {
                    @Override
                    public CompletableFuture<Void> apply(Integer i) {
                        return CompletableFuture.runAsync(() -> {
                            if (i % 5 == 0){
                                String threadName = Thread.currentThread().getName();
                                System.out.println("当前线程===" + threadName + ",被5整除了==========" + i);
                                throw new CustomerException("5000","被5整除了==========" + i);
                            } else if (i % 4 == 0){
                                String threadName = Thread.currentThread().getName();
                                System.out.println("当前线程===" + threadName + ",被4整除了==========" + i);
                                throw new CustomerException("4000","被4整除了==========" + i);
                            }
                        });
                    }
                }

        ).collect(Collectors.toList());
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[]{})).join();

        //第二种写法
        /*List<CompletableFuture<Void>> futureList = new ArrayList<>();
        list.forEach(new Consumer<Integer>() {
            @Override
            public void accept(Integer i) {
                futureList.add(CompletableFuture.runAsync(() -> {
                    if (i % 5 == 0){
                        String threadName = Thread.currentThread().getName();
                        System.out.println("当前线程===" + threadName + ",被5整除了==========" + i);
                        throw new CustomerException("5000","被5整除了==========" + i);
                    } else if (i % 4 == 0){
                        String threadName = Thread.currentThread().getName();
                        System.out.println("当前线程===" + threadName + ",被4整除了==========" + i);
                        throw new CustomerException("4000","被4整除了==========" + i);
                    }
                }));
            }
        });
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();*/
    }

    /*public static void main(String[] args) {
        CompletableFutureTest futureTest = new CompletableFutureTest();
        futureTest.comFutureTest();
    }*/

}

7、controller接口类

package com.multi.thread.controller;

import com.multi.thread.service.CompletableFutureService;
import com.multi.thread.resp.RespBody;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/future")
public class CompletableFutureController {

    @Autowired
    private CompletableFutureService futureTest;

    @GetMapping(value = "/test")
    public RespBody test(){
        futureTest.comFutureTest();
        return RespBody.ok("主线程结束");
    }

}

8、浏览器访问http://localhost:8080/future/test进行测试

根据控制台结果来看,在CompletableFuture中抛出的异常CustomerException被封装成CompletionException(继承RuntimeException),在统一异常处理器中需要加上对异常类型是否为CompletionException的判断,否则封装后抛出的CompletionException异常不能被拦截处理。

11、异常处理

接着上面样例,创建service:

package com.multi.thread.service;

import com.multi.thread.exception.CustomerException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

@Service
public class HandleExceptionService {

    public void exceptionTest(){
        List<Integer> list = new ArrayList<>();
        for (int i=1;i<10;i++){
            list.add(i);
        }
        long start = System.currentTimeMillis();

        //methodOne方法耗时11秒左右,时间由最长耗时的那个线程决定,
        //说明即使某一个线程发生了异常, 也要等待所有的线程执行完才返回到主线程中
        //methodOne(list);

        //某个线程出现异常,主线程不再等待其他后续任务,主线程继续向下执行,其他任务继续在线程池中执行
        methodTwo(list);

        long end = System.currentTimeMillis();
        System.out.println("主线程结束,执行耗时(s)=====" + (end-start)/1000);
    }

    public void methodOne(List<Integer> list){
        StringBuffer sb = new StringBuffer();
        List<CompletableFuture<Void>> futureList = list.stream().map(i -> {
            return CompletableFuture.runAsync(() -> {
                try {
                    //模拟业务耗时
                    Thread.sleep(i * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (i % 3 == 0) {
                    String threadName = Thread.currentThread().getName();
                    System.out.println("当前线程==" + threadName + ",被3整除了==" + i);
                    throw new CustomerException("5000", "被3整除了==" + i);
                } else {
                    String threadName = Thread.currentThread().getName();
                    System.out.println("当前线程==" + threadName + ",i==" + i);
                }
            }).handle(new BiFunction<Void, Throwable, Void>() {
                @Override
                public Void apply(Void result, Throwable e) {
                    if (e != null){
                        System.out.println("handle异常=====" + e);
                        sb.append(e.getCause().getMessage()).append("//");
                        throw new RuntimeException(e.getCause().getMessage());
                    }
                    return result;
                }
            });
        }).collect(Collectors.toList());
        CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0])).join();
        System.out.println("CompletableFuture处理异常====" + sb.toString());
    }

    public void methodTwo(List<Integer> list){
        StringBuffer sb = new StringBuffer();
        List<CompletableFuture<Void>> futureList = list.stream().map(i -> {
            return CompletableFuture.runAsync(() -> {
                try {
                    //模拟业务耗时
                    Thread.sleep(i * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (i % 3 == 0) {
                    String threadName = Thread.currentThread().getName();
                    System.out.println("当前线程==" + threadName + ",被3整除了==" + i);
                    throw new CustomerException("5000", "被3整除了==" + i);
                } else {
                    String threadName = Thread.currentThread().getName();
                    System.out.println("当前线程==" + threadName + ",i==" + i);
                }
            }).handle(new BiFunction<Void, Throwable, Void>() {
                @Override
                public Void apply(Void result, Throwable e) {
                    if (e != null){
                        System.out.println("handle异常=====" + e);
                        sb.append(e.getCause().getMessage()).append("//");
                        throw new RuntimeException(e.getCause().getMessage());
                    }
                    return result;
                }
            });
        }).collect(Collectors.toList());
        futureList.stream().map(CompletableFuture::join).collect(Collectors.toList());
    }

}

在controller中调用service:

package com.multi.thread.controller;

import com.multi.thread.service.CompletableFutureService;
import com.multi.thread.resp.RespBody;
import com.multi.thread.service.HandleExceptionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/future")
public class CompletableFutureController {

    @Autowired
    private CompletableFutureService futureTest;

    @Autowired
    private HandleExceptionService handleExceptionService;

    @GetMapping(value = "/test")
    public RespBody test(){
        futureTest.comFutureTest();
        return RespBody.ok("主线程结束");
    }

    @GetMapping(value = "/test2")
    public RespBody test2(){
        handleExceptionService.exceptionTest();
        return RespBody.ok("test2");
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值