¡Para lograr la programación asíncrona, debe dominar esta clase de herramienta!

prefacio

Recientemente, vi el código de la empresa, y la programación de subprocesos múltiples se usa más, incluido el uso de CompletableFuture, por lo que quiero escribir un artículo para resumir

En el desarrollo diario de proyectos Java8, CompletableFuture es una herramienta de desarrollo paralelo muy poderosa. Su sintaxis es cercana al estilo de sintaxis de java8. Usarlo junto con stream puede aumentar en gran medida la simplicidad del código.

Puede aplicarlo a su trabajo, mejorar el rendimiento de la interfaz y optimizar el código

El artículo se publicó primero en la cuenta pública (Moon with Feiyu) y luego se sincronizó con el sitio web personal: xiaoflyfish.cn/

Siento que he ganado algo, espero que les ayude les guste, reenvíenlo, gracias, gracias

introducción básica

CompletableFuture es una nueva clase en Java 8 para la programación asíncrona, que hereda Future y CompletionStage

Este futuro tiene principalmente la función de procesar el resultado de la solicitud de forma independiente. CompletionStage se utiliza para realizar el procesamiento de flujo y realizar la combinación o el procesamiento en cadena de varias etapas de solicitudes asíncronas. Por lo tanto, completableFuture puede realizar el aplanamiento y el procesamiento de flujo de toda la llamada asíncrona. interfaz y resolver el problema original Codificación compleja cuando un futuro maneja una serie de solicitudes asíncronas encadenadas

imagen

Limitaciones del Futuro

1. El resultado de Future no puede realizar más operaciones en el caso de no bloqueo

Sabemos que al usar Future, solo se puede usar el método isDone() para determinar si la tarea se completó, o el método get() puede bloquear el hilo y esperar a que regrese el resultado. No puede realizar más operaciones sin bloquear.

2. Los resultados de múltiples Futuros no se pueden combinar

Suponga que tiene varias tareas asíncronas futuras y desea realizar otras operaciones cuando se ejecuta la tarea más rápida o después de que se ejecutan todas las tareas.

3. Los futuros múltiples no pueden formar una llamada en cadena

Cuando hay dependencias entre tareas asincrónicas, Future no puede pasar el resultado de una tarea a otra tarea asincrónica y múltiples Futures no pueden crear un flujo de trabajo encadenado.

4. Sin manejo de excepciones

Ahora, usar CompletableFuture puede ayudarnos a hacer las cosas anteriores, permítanos escribir programas asincrónicos más potentes y elegantes.

uso básico

Crear tareas asíncronas

Por lo general, puede crear una tarea asíncrona utilizando los siguientes métodos estáticos de CompletableFuture

public static CompletableFuture<Void> runAsync(Runnable runnable);              //创建无返回值的异步任务
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor);     //无返回值,可指定线程池(默认使用ForkJoinPool.commonPool)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);           //创建有返回值的异步任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor); //有返回值,可指定线程池
复制代码

Ejemplo de uso:

Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    //do something
}, executor);
int poiId = 111;
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
 PoiDTO poi = poiService.loadById(poiId);
  return poi.getName();
});
// Block and get the result of the Future
String poiName = future.get();
复制代码

Usar método de devolución de llamada

通过future.get()方法获取异步任务的结果,还是会阻塞的等待任务完成

CompletableFuture提供了几个回调方法,可以不阻塞主线程,在异步任务完成后自动执行回调方法中的代码

public CompletableFuture<Void> thenRun(Runnable runnable);            //无参数、无返回值
public CompletableFuture<Void> thenAccept(Consumer<? super T> action);         //接受参数,无返回值
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn); //接受参数T,有返回值U
复制代码

使用示例:

CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                           .thenRun(() -> System.out.println("do other things. 比如异步打印日志或发送消息"));
//如果只想在一个CompletableFuture任务执行完后,进行一些后续的处理,不需要返回值,那么可以用thenRun回调方法来完成。
//如果主线程不依赖thenRun中的代码执行完成,也不需要使用get()方法阻塞主线程。
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> "Hello")
                           .thenAccept((s) -> System.out.println(s + " world"));
//输出:Hello world
//回调方法希望使用异步任务的结果,并不需要返回值,那么可以使用thenAccept方法
CompletableFuture<Boolean> future = CompletableFuture.supplyAsync(() -> {
  PoiDTO poi = poiService.loadById(poiId);
  return poi.getMainCategory();
}).thenApply((s) -> isMainPoi(s));   // boolean isMainPoi(int poiId);

future.get();
//希望将异步任务的结果做进一步处理,并需要返回值,则使用thenApply方法。
//如果主线程要获取回调方法的返回,还是要用get()方法阻塞得到
复制代码

组合两个异步任务

//thenCompose方法中的异步任务依赖调用该方法的异步任务
public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn); 
//用于两个独立的异步任务都完成的时候
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other, 
                                              BiFunction<? super T,? super U,? extends V> fn); 
复制代码

使用示例:

CompletableFuture<List<Integer>> poiFuture = CompletableFuture.supplyAsync(
  () -> poiService.queryPoiIds(cityId, poiId)
);
//第二个任务是返回CompletableFuture的异步方法
CompletableFuture<List<DealGroupDTO>> getDeal(List<Integer> poiIds){
  return CompletableFuture.supplyAsync(() ->  poiService.queryPoiIds(poiIds));
}
//thenCompose
CompletableFuture<List<DealGroupDTO>> resultFuture = poiFuture.thenCompose(poiIds -> getDeal(poiIds));
resultFuture.get();
复制代码

thenCompose和thenApply的功能类似,两者区别在于thenCompose接受一个返回CompletableFuture<U>的Function,当想从回调方法返回的CompletableFuture<U>中直接获取结果U时,就用thenCompose

如果使用thenApply,返回结果resultFuture的类型是CompletableFuture<CompletableFuture<List<DealGroupDTO>>>,而不是CompletableFuture<List<DealGroupDTO>>

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello")
  .thenCombine(CompletableFuture.supplyAsync(() -> "world"), (s1, s2) -> s1 + s2);
//future.get()
复制代码

组合多个CompletableFuture

当需要多个异步任务都完成时,再进行后续处理,可以使用allOf方法

CompletableFuture<Void> poiIDTOFuture = CompletableFuture
 .supplyAsync(() -> poiService.loadPoi(poiId))
  .thenAccept(poi -> {
    model.setModelTitle(poi.getShopName());
    //do more thing
  });

CompletableFuture<Void> productFuture = CompletableFuture
 .supplyAsync(() -> productService.findAllByPoiIdOrderByUpdateTimeDesc(poiId))
  .thenAccept(list -> {
    model.setDefaultCount(list.size());
    model.setMoreDesc("more");
  });
//future3等更多异步任务,这里就不一一写出来了

CompletableFuture.allOf(poiIDTOFuture, productFuture, future3, ...).join();  //allOf组合所有异步任务,并使用join获取结果
复制代码

该方法挺适合C端的业务,比如通过poiId异步的从多个服务拿门店信息,然后组装成自己需要的模型,最后所有门店信息都填充完后返回

这里使用了join方法获取结果,它和get方法一样阻塞的等待任务完成

多个异步任务有任意一个完成时就返回结果,可以使用anyOf方法

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
    }
    return "Result of Future 1";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
    }
    return "Result of Future 2";
});

CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(3);
    } catch (InterruptedException e) {
       throw new IllegalStateException(e);
      return "Result of Future 3";
});

CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);

System.out.println(anyOfFuture.get()); // Result of Future 2
复制代码

异常处理

Integer age = -1;

CompletableFuture<Void> maturityFuture = CompletableFuture.supplyAsync(() -> {
  if(age < 0) {
    throw new IllegalArgumentException("Age can not be negative");
  }
  if(age > 18) {
    return "Adult";
  } else {
    return "Child";
  }
}).exceptionally(ex -> {
  System.out.println("Oops! We have an exception - " + ex.getMessage());
  return "Unknown!";
}).thenAccept(s -> System.out.print(s));
//Unkown!
复制代码

exceptionally方法可以处理异步任务的异常,在出现异常时,给异步任务链一个从错误中恢复的机会,可以在这里记录异常或返回一个默认值

使用handler方法也可以处理异常,并且无论是否发生异常它都会被调用

Integer age = -1;

CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
    if(age < 0) {
        throw new IllegalArgumentException("Age can not be negative");
    }
    if(age > 18) {
        return "Adult";
    } else {
        return "Child";
    }
}).handle((res, ex) -> {
    if(ex != null) {
        System.out.println("Oops! We have an exception - " + ex.getMessage());
        return "Unknown!";
    }
    return res;
});
复制代码

分片处理

分片和并行处理:分片借助stream实现,然后通过CompletableFuture实现并行执行,最后做数据聚合(其实也是stream的方法)

CompletableFuture并不提供单独的分片api,但可以借助stream的分片聚合功能实现

举个例子:

//请求商品数量过多时,做分批异步处理
List<List<Long>> skuBaseIdsList = ListUtils.partition(skuIdList, 10);//分片
//并行
List<CompletableFuture<List<SkuSales>>> futureList = Lists.newArrayList();
for (List<Long> skuId : skuBaseIdsList) {
  CompletableFuture<List<SkuSales>> tmpFuture = getSkuSales(skuId);
  futureList.add(tmpFuture);
}
//聚合
futureList.stream().map(CompletalbleFuture::join).collent(Collectors.toList());
复制代码

举个例子

带大家领略下CompletableFuture异步编程的优势

这里我们用CompletableFuture实现水泡茶程序

En primer lugar, todavía tenemos que completar el plan de división del trabajo. En el siguiente programa, hemos dividido 3 tareas:

  • La tarea 1 es responsable de lavar el hervidor y hervir el agua.
  • La tarea 2 es responsable de lavar la tetera, lavar la taza de té y tomar las hojas de té.
  • La tarea 3 es responsable de hacer té. Entre ellos, la tarea 3 solo puede comenzar después de que se completen la tarea 1 y la tarea 2.

imagen

La siguiente es la implementación del código. Omitirá los métodos desconocidos de runAsync(), supplyAsync(), y luegoCombine() primero. Desde el punto de vista general, encontrará:

  1. No hay necesidad de mantener manualmente los subprocesos, no hay un tedioso mantenimiento manual de subprocesos y el trabajo de asignar subprocesos a tareas no requiere nuestra atención;
  2. La semántica es más clara, por ejemplo, f3 = f1.thenCombine(f2, ()->{})se puede afirmar claramente que la tarea 3 no puede comenzar hasta que se completen la tarea 1 y la tarea 2 ;
  3. El código es más conciso y se centra en la lógica empresarial, casi todo el código está relacionado con la lógica empresarial.
//任务1:洗水壶->烧开水
CompletableFuture f1 = 
  CompletableFuture.runAsync(()->{
  System.out.println("T1:洗水壶...");
  sleep(1, TimeUnit.SECONDS);

  System.out.println("T1:烧开水...");
  sleep(15, TimeUnit.SECONDS);
});
//任务2:洗茶壶->洗茶杯->拿茶叶
CompletableFuture f2 = 
  CompletableFuture.supplyAsync(()->{
  System.out.println("T2:洗茶壶...");
  sleep(1, TimeUnit.SECONDS);

  System.out.println("T2:洗茶杯...");
  sleep(2, TimeUnit.SECONDS);

  System.out.println("T2:拿茶叶...");
  sleep(1, TimeUnit.SECONDS);
  return "龙井";
});
//任务3:任务1和任务2完成后执行:泡茶
CompletableFuture f3 = 
  f1.thenCombine(f2, (__, tf)->{
    System.out.println("T1:拿到茶叶:" + tf);
    System.out.println("T1:泡茶...");
    return "上茶:" + tf;
  });
//等待任务3执行结果
System.out.println(f3.join());

void sleep(int t, TimeUnit u) {
  try {
    u.sleep(t);
  }catch(InterruptedException e){}
}
// 一次执行结果:
T1:洗水壶...
T2:洗茶壶...
T1:烧开水...
T2:洗茶杯...
T2:拿茶叶...
T1:拿到茶叶:龙井
T1:泡茶...
上茶:龙井
复制代码

Precauciones

1. Si se puede usar el grupo de subprocesos predeterminado de CompletableFuture

Los métodos estáticos runAsync y supplyAsync para crear tareas asíncronas de CompletableFuture se mencionaron anteriormente. Puede especificar el grupo de subprocesos que se usará. Si no lo especifica, use el grupo de subprocesos predeterminado de CompletableFuture.

private static final Executor asyncPool = useCommonPool ?
        ForkJoinPool.commonPool() : new ThreadPerTaskExecutor();
复制代码

Se puede ver que el grupo de subprocesos predeterminado de CompletableFuture se crea llamando al método commonPool() de ForkJoinPool. La cantidad de subprocesos centrales en este grupo de subprocesos predeterminado depende de la cantidad de núcleos de CPU. La fórmula es Runtime.getRuntime().availableProcessors() - 1, tomando 4 núcleos CPU de doble ranura como ejemplo, el número de subprocesos principales 4*2-1=7es

Tal configuración es adecuada para aplicaciones con uso intensivo de CPU, pero es riesgosa para aplicaciones con uso intensivo de IO.Cuando el qps es alto, la cantidad de subprocesos puede configurarse demasiado pequeña, lo que provocará fallas en línea.

Para que pueda personalizar el uso del grupo de subprocesos de acuerdo con la situación comercial

2. El tiempo de espera establecido no se puede serializar, de lo contrario, causará un retraso en la interfaz线程数量\*超时时间

Al final

No es fácil escribir artículos y dibujos. Si te gusta, espero que puedas ayudarme a darle me gusta y reenviarlo. Gracias.

Búsqueda de WeChat: pez volador con luna, haz amigos

Supongo que te gusta

Origin juejin.im/post/7084947359662080007
Recomendado
Clasificación