Title

Java8 CompletableFuture异步任务

引入

查询展示商品的基本信息耗时:0.5s
查询展示商品的销售信息耗时:0.7s
查询展示商品的图片信息耗时:1s
查询展示商品销售属性耗时:0.3s
查询展示商品规格属性耗时:1.5s
查询展示商品详情信息耗时:1s

即使每个查询时间耗时不多,但是加起来却需要很长耗时。为了减少线性执行造成耗时的累积,这就需要引入异步处理做优化。

Future介绍

Future是Java 5添加的类,用来描述一个异步计算的结果。

优点:

  • 可以使用 isDone 方法检查计算是否完成。
  • 使用 get 阻塞住调用线程,直到计算完成返回结果。
  • 可以使用 cancel 方法停止任务的执行。

缺点:

  • 对于结果的获取却是很不方便,只能通过阻塞或者轮询的方式得到任务的结果。
  • 阻塞的方式与我们想及时得到计算结果的期望相违背。
  • 轮询的方式会消耗大量CPU资源,并且不能及时得到计算结果。

功能:

boolean cancel(boolean mayInterruptIfRunning);

1、尝试取消执行此任务。如果任务已经完成、已被取消或由于其他原因无法取消,则此尝试将失败。如果成功,并且调用 cancel 时此任务尚未启动,则此任务永远不会运行。
2、 如果任务已经开始,则mayInterruptIfRunning 参数确定是否应中断执行此任务的线程以尝试停止任务。 参数mayInterruptIfRunning 为true ,表示执行此任务的线程应该被中断;否则,允许进行中的任务完成。
3、此方法返回后,后续调用isDone 将始终返回true。
4、如果此方法返回true,则对isCancelled 的后续调用将始终返回true。

boolean isCancelled();

如果此任务在正常完成之前被取消,则返回true,否则返回false。

boolean isDone();

如果此任务完成,则返回 true。任务完成可能是由于正常终止、异常或取消——在所有这些情况下,此方法将返回 true。

V get() throws InterruptedException, ExecutionException;

阻塞直至任务完成,然后检索其结果。
throws CancellationException:如果计算被取消
throws ExecutionException:如果计算抛出异常
throws InterruptedException:如果当前线程在等待时被中断

V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

如有必要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。
参数timeout:等待的最长时间
参数unit:超时参数的时间单位
throws CancellationException:如果计算被取消
throws ExecutionException:如果计算抛出异常
throws InterruptedException:如果当前线程在等待时被中断
throws TimeoutException:如果等待超时

CompletableFuture介绍

在Java 8中, 新增加了一个包含50个方法左右的类:CompletableFuture,提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,并且提供了转换和组合CompletableFuture的方法。
CompletableFuture类实现了Future接口,所以你还是可以像以前一样通过get方法阻塞或者轮询的方式获得结果,但是这种方式不推荐使用。

创建异步对象(runAsync、supplyAsync)

CompletableFuture提供了四个静态方法来创建一个异步操作。
(带有Async默认是异步执行的。这里所谓的异步指的是不在当前线程内执行。)

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)

方法分为两类:

  • runAsync 没有返回结果
  • supplyAsync 有返回结果

测试代码:

public class CompletableFutureDemo {

   // corePoolSize:线程池的核心线程数量 线程池创建出来后就会 new Thread() 5个
   // maximumPoolSize:最大的线程数量,线程池支持的最大的线程数
   // keepAliveTime:存活时间,当线程数大于核心线程,空闲的线程的存活时间 50-5=45
   // unit:存活时间的单位
   // BlockingQueue<Runnable> workQueue:阻塞队列 当线程数超过了核心线程数据,那么新的请求到来的时候会加入到阻塞的队列中
   // new LinkedBlockingQueue<>() 默认队列的长度是 Integer.MAX 那这个就太大了,所以我们需要指定队列的长度
   // threadFactory:创建线程的工厂对象
   // RejectedExecutionHandler handler:当线程数大于最大线程数的时候会执行的淘汰策略
   private static ThreadPoolExecutor executor = new ThreadPoolExecutor(
           5,
           50,
           10,
           TimeUnit.SECONDS,
           new LinkedBlockingDeque(1000),
           Executors.defaultThreadFactory(),
           new ThreadPoolExecutor.AbortPolicy()
   );

   public static void main(String[] args) throws ExecutionException, InterruptedException {
       System.out.println("main方法开始了…………");
       CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(() -> {
           System.out.println("线程开始了...");
           System.out.println("当前线程---->" + Thread.currentThread().getName());
           int i = 100 / 50;
           System.out.println("线程结束了...");
       }, executor);
       System.out.println("main方法结束了…………");

       System.out.println("-----------------------------");
       CompletableFuture<Integer> integerCompletableFuture = CompletableFuture.supplyAsync(() -> {
           System.out.println("线程开始了...");
           System.out.println("当前线程---->" + Thread.currentThread().getName());
           int i = 100 / 50;
           System.out.println("线程结束了...");
           return i;
       }, executor);
       System.out.println("integerCompletableFuture=" + integerCompletableFuture.get());
   }
}

测试结果:

当CompletableFuture的计算结果完成,或者抛出异常的时候,可以执行特定的Action。主要是下面的方法:

// 可以获取异步任务的返回值和抛出的异常信息,但是不能修改返回结果
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);

// 当异步任务跑出了异常后会触发的方法,如果没有抛出异常该方法不会执行
// 拓展—>completeExceptionally​: 如果完成动作过程中抛出异常,将导致对get()和相关方法的调用引发给定的异常
// 简而言之,future.completeExceptionally​(e)是在完成过程中主动设置异常信息。(如果future未完成,抛出主动设置的异常信息。反之,则返回完成后的结果。)
// 拓展—>obtrudeException: 强制导致方法get()和相关方法的后续调用抛出给定的异常,无论是否已完成。
public CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn);

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

具体信息参考文章:https://blog.csdn.net/qq_52793248/article/details/128399944

无返回值调用

import java.util.concurrent.CompletableFuture;
 
public class TestDemo {
 
    public static void main(String[] args) {
        System.out.println("进入主线程=============");
        CompletableFuture.runAsync(()->getNum());
        System.out.println("主线程结束=============");
    }
	
	public Integer getNum(){
		return 5 / 2;
	}
}

有返回值调用

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
 
public class TestDemo {
 
    public static void main(String[] args) throws ExecutionException, InterruptedException {
	  CompletableFuture<Integer> future = new CompletableFuture<>();
			new Thread(() -> {
				try {
					Integer num = getNum();
					future.complete(num);
				} catch (Exception ex) {
					future.completeExceptionally(ex); //抛出导致失败的异常,完成这次Future操作
				}

			}).start();
		System.out.println(future.get());
    }

   private static Integer getNum() {
        return 5 / 2;
    }
}

与下面等价,下面写法更简单

import java.util.concurrent.CompletableFuture;
 
public class TestDemo {
 
    public static void main(String[] args) throws ExecutionException, InterruptedException {
		// 无返回值 runAsync() 有返回值 supplyAsync()
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> getNum());
        System.out.println(future.get());
    }

    private static Integer getNum() {
        return 5 / 2;
    }
}

使用自定义线程池

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestDemo {
    /**
     * 自定义线程池
     */
    public static ExecutorService service = Executors.newFixedThreadPool(5);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
		// 无返回值 runAsync() 有返回值 supplyAsync()
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> getNum(),service);
        System.out.println(future.get());
    }

    private static Integer getNum() {
        return 5 / 2;
    }
}

处理异常

handleAsync
该方法有两个参数,一种一个是上一步的结果,另一个则是上一步的异常。而且如果出现异常则第一个参数为null,如果没有异常则第二个参数为null。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestDemo {
    /**
     * 自定义线程池
     */
    public static ExecutorService service = Executors.newFixedThreadPool(5);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
		// 无返回值 runAsync() 有返回值 supplyAsync()
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> getNum(),service).handleAsync((res, e) -> {
            if (res != null) {
                return res;
            }
            if (e != null) {
                return 0;
            }
            return 0;
        });
        System.out.println(future.get());
    }

    private static Integer getNum() {
	    // by zero 
        return 5 / 0;
    }
}

也可以使用 exceptionally 对异常进行单独处理。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TestDemo {
    /**
     * 自定义线程池
     */
    public static ExecutorService service = Executors.newFixedThreadPool(5);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
		// 无返回值 runAsync() 有返回值 supplyAsync()
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> getNum(),service).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 0;
        });
        System.out.println(future.get());
    }

    private static Integer getNum() {
	    // by zero 
        return 5 / 0;
    }
}

多任务组合

使用CompletableFuture主要就是为了完成异步任务的编排。所以第三步也是我们最重要的应用。常见的多个任务组合会有以下几种情况

1.业务二会用到业务一的结果

针对第一种情况,这个直接使用上一步handle直接对结果进行处理就好。相对比较简单。

2.业务三需要等到业务一和业务二都完成

定义:
业务一 future
业务一 返回值f1

业务二 future2
业务二 返回值f2

如果不需要接收业务一和业务二的返回值,并且自己也不用返回值那么就可以使用:future.runAfterBothAsync(future2,() ->{});

如果需要使用业务一和业务二的返回值,但是在业务三不需要返回值那么可以使用:future.thenAcceptBothAsync(future2,(f1,f2) ->{},service);

如果需要使用业务一和业务二的返回值,并且自己也需要有返回值则使用:future.thenCombineAsync(future2, (f1, f2) -> { return f1 + f2; }, service);

3.业务三需要等到业务一或者业务二其中之一完成

定义:
业务一 future
业务一 返回值f1

业务二 future2
业务二 返回值f2

如果不需要接收业务一或者业务二的返回值,并且自己也不用返回值那么就可以使用:future.runAfterEitherAsync(future2,()->{});

如果需要使用业务一或者业务二的返回值,但是在业务三不需要返回值那么可以使用:future.acceptEitherAsync(future2,(f)->{});

如果需要使用业务一或者业务二的返回值,并且自己也需要有返回值则使用:future.applyToEitherAsync(future2,(f)->{return 0;});

具体情况可以根据下面的代码修改测试:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class CompletableFutureTest {
 
    public static ExecutorService service = Executors.newFixedThreadPool(5);
 
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        System.out.println("进入主线程============= ");
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
            int k = 10 / 2;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("计算结果k = " + k);
            return k;
        },service);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
            int k = 10 * 2;
            System.out.println("计算结果k2 = " + k);
            return k;
        },service);
        CompletableFuture<Integer> combineAsync = future.thenCombineAsync(future2, (f1, f2) -> {
            System.out.println("最终执行任务3:" + f1 + "===========" + f2);
            return f1 + f2;
        }, service);
        Integer integer = combineAsync.get();
        System.out.println("integer = " + integer);
        System.out.println("主线程结束============= ");
    }
}

补充:此外还有allOf 和anyOff ,这两个方法可以传入多个任务。同理,allOf是等待所有任务都完成了,anyOf是有任意一个任务完成即可。

posted @ 2024-11-14 15:59  快乐小洋人  阅读(16)  评论(0编辑  收藏  举报