JUC并发—13.Future模式和异步编程简介

大纲

1.Runnable接口与Callable接口

(1)Runnable接口实现异步任务

(2)Callable接口实现异步任务

2.Future模式

(1)Future模式的概念

(2)Future接口的使用

(3)FutureTask类的使用

3.CompletableFuture的使用和异步编程

(1)使用Future时的问题

(2)CompletableFuture的使用例子

(3)CompletableFuture的使用场景

(4)CompletableFuture的创建异步任务

(5)CompletableFuture的简单任务异步回调

(6)CompletableFuture的多个任务组合处理

(7)CompletableFuture的使用注意事项

1.Runnable接口与Callable接口

(1)Runnable接口实现异步任务

(2)Callable接口实现异步任务

(1)Runnable接口实现异步任务

也就是通过创建实现了Runnable接口的Thread线程来实现异步任务。

Runnable接口实现的异步任务存在的问题:

一.Runnable接口不支持获取返回值

二.Runnable接口不支持抛出异常

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

public class Thread implements Runnable {
    ...
    private Runnable target;
    
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    ...
}

@RunWith(SpringRunner.class)
@SpringBootTest
public class Test {
    @Test
    public void testNewThread() {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("实现的异步任务");
            }
        });
        t1.start();
    }
}

(2)Callable接口实现异步任务

Callable接口需要与Future和ExecutorService结合使用:通过ExecutorService的submit()方法提交一个实现Callable接口的任务,然后ExecutorService的submit()方法会返回一个实现Future接口的对象,接着调用Future接口的get()方法就可以获取异步任务的结果。

public interface ExecutorService extends Executor {
    ...
    //Submits a value-returning task for execution and returns a Future representing the pending results of the task. 
    //The Future's get method will return the task's result upon successful completion.
    //@param task the task to submit
    //@param <T> the type of the task's result
    //@return a Future representing pending completion of the task
    <T> Future<T> submit(Callable<T> task);
    ...
}

public interface Future<V> {
    ...
    //Waits if necessary for the computation to complete, and then retrieves its result.
    V get() throws InterruptedException, ExecutionException;
    
    //Waits if necessary for at most the given time for the computation to complete, and then retrieves its result, if available.
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    ...
}

2.Future模式

(1)Future模式的概念

(2)Future接口的使用

(3)FutureTask类的使用

(1)Future模式的概念

当前线程有一个任务,提交给了Future,由Future来完成这个任务,在此期间当前线程可以处理其他事情了。一段时间后,当前线程就可以从Future中获取结果。

(2)Future接口的使用

一.Future接口源码

二.普通模式计算1000次1到1亿的和

三.Future模式计算1000次1到1亿的和

一.Future接口源码

Future就是对实现Runnable或Callable接口的任务进行查询、中断、获取。

public interface Future<V> {
    //用来取消任务,取消成功则返回true,取消失败则返回false
    //mayInterruptIfRunning参数表示是否允许取消正在执行却没有执行完毕的任务,设为true,则表示可以取消正在执行过程中的任务
    //如果任务已完成,则无论mayInterruptIfRunning为true还是false,此方法都返回false,即如果取消已经完成的任务会返回false
    //如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false
    //如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
    boolean cancel(boolean mayInterruptIfRunning);

    //表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回true
    boolean isCancelled();

    //表示任务是否已经完成,若任务完成,则返回true
    boolean isDone();

    //获取执行结果,如果最终结果还没得出该方法会产生阻塞,直到任务执行完毕返回结果
    V get() throws InterruptedException, ExecutionException;

    //获取执行结果,如果在指定时间内,还没获取到结果,则抛出TimeoutException
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

二.普通模式计算1000次1到1亿的和

public class NormalTest {
    //普通模式计算1000次1到1亿的和
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        List<Integer> retList = new ArrayList<>();
        //计算1000次1至1亿的和
        for(int i = 0; i < 1000; i++) {
            retList.add(Calc.cal(100000000));
        }
        System.out.println("耗时: " + (System.currentTimeMillis() - start));
        for (int i = 0; i < 1000; i++) {
            try {
                Integer result = retList.get(i);
                System.out.println("第" + i + "个结果: " + result);
            } catch (Exception e) {          
            }
        }
        System.out.println("耗时: " + (System.currentTimeMillis() - start));
    }

    public static class Calc implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            return cal(10000);
        }
        public static int cal (int num) {
            int sum = 0;
            for (int i = 0; i < num; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

--------------------------------------------------
执行结果:
耗时: 43659
第0个结果: 887459712
第1个结果: 887459712
第2个结果: 887459712
...
第999个结果: 887459712
耗时: 43688

三.Future模式计算1000次1到1亿的和

public class FutureTest {
    //Future模式计算1000次1到1亿的和
    public static void main(String[] args) {
        long start = System.currentTimeMillis();
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<Integer>> futureList = new ArrayList<>();
        //计算1000次1至1亿的和
        for (int i = 0; i < 1000; i++) {
            //调度执行
            futureList.add(executorService.submit(new Calc()));
        }
        System.out.println("耗时: " + (System.currentTimeMillis() - start));
        for (int i = 0; i < 1000; i++) {
            try {
                Integer result = futureList.get(i).get();
                System.out.println("第" + i + "个结果: " + result);
            } catch (InterruptedException | ExecutionException e) {
            }
        }
        System.out.println("耗时: " + (System.currentTimeMillis() - start));
    }

    public static class Calc implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            return cal(100000000);
        }

        public static int cal (int num) {
            int sum = 0;
            for (int i = 0; i < num; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

--------------------------------------------------
执行结果:
耗时: 12058
第0个结果: 887459712
第1个结果: 887459712
...
第999个结果: 887459712
耗时: 12405

(3)FutureTask类的使用

一.FutureTask类的简介

二.Callable + Future获取异步任务的执行结果

三.Callable + FutureTask获取异步任务的结果

一.FutureTask类的简介

FutureTask类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口。所以FutureTask类既可以作为Runnable被线程执行,又可以作为Future得到Callable的run()方法的返回值。同时,FutureTask类是Future接口的唯一实现类。

public class FutureTask<V> implements RunnableFuture<V> {
    ...
    ...
}

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

二.Callable + Future获取异步任务的执行结果

//Callable+Future获取执行结果
public class FutureTest {
    public static void main(String[] args) {
    		ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        executor.shutdown();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
        System.out.println("主线程在执行任务");
        try {
            System.out.println("task运行结果" + result.get());
        } catch (InterruptedException | 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;
    }
}

三.Callable + FutureTask获取异步任务的结果

//Callable + FutureTask获取执行结果
public class FutureTest {
    public static void main(String[] args) {
        //第一种方式
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<>(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 {
            System.out.println("task运行结果" + futureTask.get());
        } catch (InterruptedException | 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;
    }
}

3.CompletableFuture的使用和异步编程

(1)使用Future时的问题

(2)CompletableFuture的使用例子

(3)CompletableFuture的使用场景

(4)CompletableFuture的创建异步任务

(5)CompletableFuture的简单任务异步回调

(6)CompletableFuture的多个任务组合处理

(7)CompletableFuture的使用注意事项

(1)使用Future时的问题

一.通过Future获取结果演示

如果主线程需要执行一个很耗时的计算任务,那么可通过Future把该任务放到异步线程执行,让主线程继续处理其他任务。当这个耗时的任务处理完成后,再让主线程通过Future获取计算结果。

如下所示,有两个服务:

public class UserInfoService {
    public UserInfo getUserInfo(Long userId) throws InterruptedException {
        Thread.sleep(300);//模拟调用耗时
        return new UserInfo("...");//一般是查数据库,或者远程调用返回
    }
}

public class OrderService {
    public OrderInfo getOrderInfo(long userId) throws InterruptedException {
        Thread.sleep(500);//模拟调用耗时
        return new OrderInfo("...");
    }
}

接下来,演示在主线程中是如何使用Future来进行异步调用的。

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        UserInfoService userInfoService = new UserInfoService();
        OrderService orderService = new OrderService();
        long userId = 1L;
        long startTime = System.currentTimeMillis();
        
        //调用用户服务获取用户基本信息
        FutureTask<UserInfo> userInfoFutureTask = new FutureTask<>(new Callable<UserInfo>() {
            @Override
            public UserInfo call() throws Exception {
                return userInfoService.getUserInfo(userId);
            }
        });
        //提交任务给线程池异步执行
        executorService.submit(userInfoFutureTask);
        
        //模拟主线程其它操作耗时
        Thread.sleep(300);
        
        //调用订单服务获取用户订单信息
        FutureTask<OrderInfo> orderInfoFutureTask = new FutureTask<>(new Callable<OrderInfo>() {
            @Override
            public MedalInfo call() throws Exception {
                return medalService.getMedalInfo(userId);
            }
        });
        //提交任务给线程池异步执行
        executorService.submit(medalInfoFutureTask);
        
        UserInfo userInfo = userInfoFutureTask.get();//获取用户信息结果
        OrderInfo orderInfo = orderInfoFutureTask.get();//获取订单信息结果
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}
//总共用时806ms

如果不使用Future进行并行异步调用,而是在主线程串行执行,那么耗时大约为300 + 500 + 300 = 1100ms。

二.Future获取结果时存在的问题

可见,Future + 线程池异步配合,提高了程序的执行效率。但是由于根据Future获取结果的方式不是很友好,所以只能通过阻塞或者轮询的方式来得到任务的结果。

方式一:

通过Future提供的get()方法,进行阻塞调用。在主线程获取到异步任务的执行结果前,get()方法会一直阻塞。

方式二:

通过Future提供的isDone()方法,进行轮询调用。可以让主线程在程序中轮询isDone()方法来查询异步任务的执行结果。

阻塞的方式会违背异步编程的理念,轮询的方式又会空耗CPU资源,因此JDK8设计出了CompletableFuture。

CompletableFuture提供了一种类似观察者模式的机制,可以让异步任务执行完成后通知主线程。

三.使用Future的问题总结

首先需要单独创建一个线程池来提交Callable任务。然后如果使用Future的get()方法获取结果,那么需要进行阻塞调用。如果使用Future的isDone()方法获取结果,那么需要进行轮询调用。

(2)CompletableFuture的使用例子

如下所示,使用CompletableFuture,代码简洁了很多。CompletableFuture的supplyAsync()方法,提供了异步执行的功能,线程池也不用单独创建了,实际上使用的默认线程池是ForkJoinPool.commonPool。

public class CompletableFutureTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException {
        UserInfoService userInfoService = new UserInfoService();
        OrderService orderService = new OrderService();
        long userId = 1L;
        long startTime = System.currentTimeMillis();
        
        //调用用户服务获取用户基本信息
        CompletableFuture<UserInfo> completableUserInfoFuture =
            CompletableFuture.supplyAsync(() -> userInfoService.getUserInfo(userId));
        
        //模拟主线程其它操作耗时
        Thread.sleep(300);
        
        //调用订单服务获取用户订单信息
        CompletableFuture<OrderInfo> completableOrderInfoFuture = 
            CompletableFuture.supplyAsync(() -> orderService.getOrderInfo(userId));

        //获取个人信息结果
        UserInfo userInfo = completableUserInfoFuture.get();
        //获取订单信息结果
        OrderInfo orderInfo = completableOrderInfoFuture.get();
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }
}

(3)CompletableFuture的使用场景

一.创建异步任务

二.简单任务异步回调

三.多个任务组合处理

(4)CompletableFuture的创建异步任务

CompletableFuture创建异步任务的方法:supplyAsync()和runAsync();

一.supplyAsync()方法执行CompletableFuture任务,有返回值。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    ...
    //Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool#commonPool() 
    //with the value obtained by calling the given Supplier.
    //@param supplier a function returning the value to be used to complete the returned CompletableFuture
    //@param <U> the function's return type
    //@return the new CompletableFuture
    //使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

    //Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor 
    //with the value obtained by calling the given Supplier.
    //@param supplier a function returning the value to be used to complete the returned CompletableFuture
    //@param executor the executor to use for asynchronous execution
    //@param <U> the function's return type
    //@return the new CompletableFuture
    //使用自定义的线程池,根据supplier构建执行任务
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }
    ...
}

二.runAsync()方法执行CompletableFuture任务,没有返回值。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    ...
    //Returns a new CompletableFuture that is asynchronously completed by a task running in the ForkJoinPool#commonPool() 
    //after it runs the given action.
    //@param runnable the action to run before completing the returned CompletableFuture
    //@return the new CompletableFuture
    //使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

    //Returns a new CompletableFuture that is asynchronously completed by a task running in the given executor 
    //after it runs the given action.
    //@param runnable the action to run before completing the returned CompletableFuture
    //@param executor the executor to use for asynchronous execution
    //@return the new CompletableFuture
    //使用自定义的线程池,根据runnable构建执行任务
    public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
    }
    ...
}

使用示例如下:

public class CompletableFuture {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        
        //runAsync的使用,没有返回值
        CompletableFuture<Void> runFuture =
            CompletableFuture.runAsync(() -> System.out.println("没有返回值"), executor);
        //supplyAsync的使用,有返回值
        CompletableFuture<String> supplyFuture =
            CompletableFuture.supplyAsync(() -> { System.out.print("有返回值"); return "OK"; }, executor);
        
        //runAsync的future没有返回值,输出null
        System.out.println(runFuture.join());
        //supplyAsync的future,有返回值
        System.out.println(supplyFuture.join());
        
        //关闭线程池
        executor.shutdown();
    }
}

(5)CompletableFuture的简单任务异步回调

一.thenRun()和thenRunAsync()方法

CompletableFuture的thenRun()方法就是:执行完第一个任务后,再执行第二个任务。也就是当某个任务执行完成后,会执行设置给该任务的回调方法。但是前后两个任务没有传递参数,第二个任务也没有返回值。

thenRun()和thenRunAsync()方法的区别是:如果执行第一个任务时,传入了一个自定义线程池。当调用thenRun()方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。当调用thenRunAsync()方法执行第二个任务时,则第一个任务使用传入的线程池,第二个任务使用ForkJoin线程池。也就是说,thenRunAsync()会使用ForkJoin线程池来异步执行任务。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    ...
    public CompletableFuture<Void> thenRun(Runnable action) {
        return uniRunStage(null, action);
    }
    public CompletableFuture<Void> thenRunAsync(Runnable action) {
        return uniRunStage(asyncPool, action);
    }
    ...
}

public class FutureThenRunTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("先执行第一个任务");
            return "第一个任务执行完成";
        });
        CompletableFuture thenRunFuture = orgFuture.thenRun(() -> {
            System.out.println("接着执行第二个任务");
        });
        System.out.println("输出" + thenRunFuture.get());
    }
}

//执行程序输出的结果如下:
//先执行第一个任务
//接着执行第二个任务
//输出null

二.thenAccept()和thenAcceptAsync()方法

CompletableFuture的thenAccept()方法表示:第一个任务执行完成后,执行第二个任务(回调方法)时,会将第一个任务的执行结果作为入参,传递到第二个任务中,但是第二个任务是没有返回值的。

CompletableFuture的thenAcceptAsync()方法会使用ForkJoin线程池来异步执行任务。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    ...
    public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
        return uniAcceptStage(null, action);
    }
    public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
        return uniAcceptStage(asyncPool, action);
    }
    ...
}

public class FutureThenAcceptTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行第一个任务");
            return "第一个任务的返回值";
        });
        CompletableFuture thenAcceptFuture = orgFuture.thenAccept((a) -> {
            System.out.println("执行第二个任务");
            if ("第一个任务的返回值".equals(a)) {
                System.out.println("收到传入的第一个任务的返回值");
            }
        });
        System.out.println("输出" + thenAcceptFuture.get());
    }
}

//执行程序输出的结果如下:
//执行第一个任务
//执行第二个任务
//收到传入的第一个任务的返回值
//输出null

三.thenApply()和thenApplyAsync()方法

CompletableFuture的thenApply()方法表示:第一个任务执行完成后,执行第二个任务(回调方法)时,会将第一个任务的执行结果作为入参,传递到第二个任务中,并且第二个任务是有返回值的。

CompletableFuture的thenApplyAsync()方法会使用ForkJoin线程池来异步执行任务。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    ...
    public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) {
        return uniApplyStage(null, fn);
    }
    public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn) {
        return uniApplyStage(asyncPool, fn);
    }
    ...
}

public class FutureThenApplyTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行第一个任务");
            return "第一个任务的返回值";
        });
        CompletableFuture<String> thenApplyFuture = orgFuture.thenApply((a) -> {
            if ("第一个任务的返回值".equals(a)) {
                System.out.println("收到传入的第一个任务的返回值");
                System.out.println("执行第二个任务");
                return "第二个任务的返回值";
            }
            return "第二个任务的返回值";
        });
        System.out.println("输出" + thenApplyFuture.get());
    }
}

//执行程序输出的结果如下:
//执行第一个任务
//收到传入的第一个任务的返回值
//执行第二个任务
//输出第二个任务的返回值

四.exceptionally()方法

CompletableFuture的exceptionally()方法表示:某个任务执行异常时,才执行的回调方法。并且将抛出的异常作为参数,传递到回调方法中。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    ...
    public CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn) {
        return uniExceptionallyStage(fn);
    }
    ...
}

public class FutureExceptionTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行任务时抛出异常");
            throw new RuntimeException();
        });
        CompletableFuture<String> exceptionFuture = orgFuture.exceptionally((e) -> {
            e.printStackTrace();
            return "返回处理异步执行任务抛出的异常的结果";
        });
        System.out.println(exceptionFuture.get());
    }
}

//执行程序输出的结果如下:
//异步执行任务时抛出异常
//java.util.concurrent.CompletionException: java.lang.RuntimeException
//返回处理异步执行任务抛出的异常的结果

五.whenComplete()和whenCompleteAsync()

CompletableFuture的whenComplete()方法表示:某个任务执行完成后,紧接着执行的回调方法无返回值。whenComplete()方法返回的CompletableFuture的result是上个任务的结果。

CompletableFuture的whenCompleteAsync()方法会使用ForkJoin线程池来异步执行回调方法。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    ...
    
    public CompletableFuture<T> whenComplete(BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(null, action);
    }

    public CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) {
        return uniWhenCompleteStage(asyncPool, action);
    }
    ...
}

public class FutureWhenTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步执行第一个任务");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "第一个任务的返回值";
        });
        CompletableFuture<String> rstFuture = orgFuture.whenComplete((a, throwable) -> {
            System.out.println("异步执行第二个任务");
            if ("第一个任务的返回值".equals(a)) {
                System.out.println("收到传入的第一个任务的返回值");
            }
            System.out.println("whenComplete()执行的回调方法没有返回值");
        });
        System.out.println("输出" + rstFuture.get());
    }
}

//执行程序输出的结果如下:
//异步执行第一个任务
//异步执行第二个任务
//收到传入的第一个任务的返回值
//whenComplete()执行的回调方法没有返回值
//输出第一个任务的返回值

六.handle()和handleAsync()方法

CompletableFuture的handle()方法表示:异步任务执行完成后,紧接着执行的回调方法是有返回值的。handle()方法返回的CompletableFuture的result是回调方法执行的结果。

CompletableFuture的handleAsync()方法会使用ForkJoin线程池来异步执行回调方法。

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    private static final Executor asyncPool = useCommonPool ? ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
    ...
    
    public <U> CompletableFuture<U> handle(BiFunction<? super T, Throwable, ? extends U> fn) {
        return uniHandleStage(null, fn);
    }

    public <U> CompletableFuture<U> handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) {
        return uniHandleStage(asyncPool, fn);
    }
    ...
}

public class FutureHandlerTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> orgFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "第一个任务的返回值";
        });
        CompletableFuture<String> rstFuture = orgFuture.handle((a, throwable) -> {
            System.out.println("执行第二个任务");
            if ("第一个任务的返回值".equals(a)) {
                System.out.println("收到传入的第一个任务的返回值");
                return "第二个任务的返回值";
            }
            return "第二个任务的返回值";
        });
        System.out.println("输出" + rstFuture.get());
    }
}

//执行程序输出的结果如下:
//执行第二个任务
//收到传入的第一个任务的返回值
//输出第二个任务的返回值

(6)CompletableFuture的多个任务组合处理

一.AND组合关系

thenCombine()、thenAcceptBoth()、runAfterBoth()都表示:将两个CompletableFuture任务组合起来,只有这两个任务都正常执行完后,才会执行后面的回调方法。区别如下:

thenCombine()方法会将两个任务的执行结果作为方法入参,传递到指定的回调方法中,且指定的回调方法有返回值。

thenAcceptBoth()方法会将两个任务的执行结果作为方法入参,传递到指定的回调方法中,但指定的回调方法无返回值。

runAfterBoth()方法则不会把两个任务的执行结果当做方法入参,传递到指定的回调方法中,且指定的回调方法没有返回值。

public class ThenCombineTest {
    public static void main(String[] args) throws Exception {
        CompletableFuture<String> firstFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("第一个异步任务要执行3秒");
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第一个异步任务执行完毕");
            return "第一个任务的返回值";
        });
        ExecutorService executor = Executors.newFixedThreadPool(2);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("第二个异步任务要执行2秒");
            try {
                Thread.sleep(2000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第二个异步任务执行完毕");
            return "第二个任务的返回值";
        }, executor).thenCombineAsync(firstFuture, (secondResult, firstResult) -> {
            System.out.println("两个异步任务都执行完毕后才能执行这里");
            System.out.println("接收到" + firstResult);
            System.out.println("接收到" + secondResult);
            return "两个异步任务都执行完后执行的回调的返回值";
        }, executor);
        System.out.println("输出" + future.join());
        executor.shutdown();
    }
}

//执行程序输出的结果如下:
//第一个异步任务要执行3秒
//第二个异步任务要执行2秒
//第二个异步任务执行完毕
//第一个异步任务执行完毕
//两个异步任务都执行完毕后才能执行这里
//接收到第一个任务的返回值
//接收到第二个任务的返回值
//输出两个异步任务都执行完后执行的回调的返回值

二.OR组合关系

applyToEither()、acceptEither()、runAfterEither()都表示:将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。区别如下:

applyToEither()方法会将已经执行完成的任务的结果,作为方法入参,传递到指定的回调方法中,且指定的回调方法有返回值。

acceptEither()方法会将已经执行完成的任务的结果,作为方法入参,传递到指定的回调方法中,且指定的回调方法无返回值。

runAfterEither()方法不会把已经执行完成的任务的结果当做方法入参,传递到指定的回调方法中,且指定的回调方法没有返回值。

public class AcceptEitherTest {
    public static void main(String[] args) {
        //第一个异步任务,休眠2秒,保证它执行晚点
        CompletableFuture<String> first = CompletableFuture.supplyAsync(() -> {
            try{
                Thread.sleep(2000L);
                System.out.println("执行完第一个任务");
            } catch (Exception e) {
                return "执行第一个任务异常";
            }
            return "返回第一个任务的结果";
        });
        ExecutorService executor = Executors.newSingleThreadExecutor();
        //第二个异步任务
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行完第二个任务");
            return "返回第二个任务的结果";
        }, executor).acceptEitherAsync(first, (lastResult) -> {
            System.out.println("执行完第一个或第二个任务后的回调");
            System.out.println("获取到传入的先执行完的任务的返回结果是:" + lastResult);
        }, executor);
        executor.shutdown();
    }
}

//执行程序输出的结果如下:
//执行完第二个任务
//执行完第一个或第二个任务后的回调
//获取到传入的先执行完的任务的返回结果是:返回第二个任务的结果

三.anyOf

任意一个任务执行完,就执行anyOf()方法返回的CompletableFuture。如果执行的任务异常,anyOf()方法返回的CompletableFuture在执行get()方法时,会抛出异常。

public class AnyOfFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> a = CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(3000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务A执行完了");
        });
        CompletableFuture<Void> b = CompletableFuture.runAsync(() -> {
            System.out.println("任务B执行完了");
        });
        CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(a, b).whenComplete((m, k) -> {
            System.out.println("finish");
        });
        anyOfFuture.join();
    }
}

//执行程序输出的结果如下:
//任务B执行完了
//finish

四.allOf

所有任务都执行完成后,才执行allOf()方法返回的CompletableFuture。如果任意一个任务异常,allOf()方法返回的CompletableFuture在执行get()方法时,会抛出异常。

public class allOfFutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Void> a = CompletableFuture.runAsync(() -> {
            System.out.println("任务A执行完了");
        });
        CompletableFuture<Void> b = CompletableFuture.runAsync(() -> {
            System.out.println("任务B执行完了");
        });
        CompletableFuture<Void> allOfFuture = CompletableFuture.allOf(a, b).whenComplete((m, k) -> {
            System.out.println("finish:" + m + "," + k);
        });
    }
}

//执行程序输出的结果如下:
//任务A执行完了
//任务B执行完了
//finish: null,null

五.thenCompose

thenCompose()方法会在某个任务执行完成后,将该任务的执行结果作为方法入参去执行指定的方法。thenCompose()方法会返回一个新的CompletableFuture实例。

public class ThenComposeTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<String> f = CompletableFuture.completedFuture("第一个任务");
        ExecutorService executor = Executors.newSingleThreadExecutor();
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("执行第二个任务");
            return "返回第二个任务的结果";
        }, executor).thenComposeAsync(data -> {
            System.out.println("执行第三个任务");
            System.out.println("收到传入的:" + data);
            return f;
        }, executor);
        System.out.println(future.join());
        executor.shutdown();
    }
}

//执行程序输出的结果如下:
//执行第二个任务
//执行第三个任务
//收到传入的:返回第二个任务的结果
//第一个任务

(7)CompletableFuture的使用注意事项

一.Future需要获取返回值,才能获取异常信息

public class ThenComposeTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = newThreadPoolExecutor(
            5,
            10,
            5L,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(10)
        );
        CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
            int a = 0;
            int b = 666;
            int c = b / a;
            return true;
        },executorService).thenAccept((a) -> {
            System.out.println(a);
        });

        //如果如下这一行get()方法,是看不到异常信息的
        //future.get();
    }
}

二.CompletableFuture的get()方法是阻塞的

CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。

三.默认线程池的注意点

CompletableFuture代码中使用了默认的线程池,处理的线程个数是机器CPU核数 - 1。在大量请求过来时,如果处理逻辑复杂,那么响应就会很慢。所以一般建议使用自定义线程池,优化线程池配置参数。

四.自定义线程池时注意饱和策略

由于CompletableFuture的get()方法是阻塞的,所以一般建议使用类似future.get(3, TimeUnit.SECONDS),并且一般建议使用自定义线程池。

但如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,那么当线程池饱和时,会直接丢弃任务,不会抛出异常。

因此建议CompletableFuture线程池的拒绝策略最好使用AbortPolicy,然后对耗时的异步线程做好线程池隔离。

相关推荐
东阳马生架构1 天前
JUC并发—12.ThreadLocal源码分析
juc并发
东阳马生架构2 天前
JUC并发—11.线程池源码分析
juc并发
东阳马生架构3 天前
JUC并发—10.锁优化与锁故障
juc并发
东阳马生架构3 天前
JUC并发—9.并发安全集合四
java·juc并发·并发安全的集合
东阳马生架构4 天前
JUC并发—9.并发安全集合三
java·juc并发·并发安全的集合
东阳马生架构4 天前
JUC并发—9.并发安全集合二
juc并发
东阳马生架构4 天前
JUC并发—8.并发安全集合二
juc并发