Cómo escribir un código asíncrono elegante - CompletableFuture

prefacio

En nuestra conciencia, ejecución síncrona en el programa están más de acuerdo con la forma de pensar, pero por lo general no es un proceso asíncrono buena cosa. En el caso de una devolución de llamada operación de cálculo asíncrono representado tienden dispersado en el código, puede estar anidada dentro de la otra, si se requiere un paso en el que el error de procesamiento puede ocurrir, la situación se hace peor. Java 8 introduce muchas nuevas características, que contendrá la CompletableFuture clase de introducción, que nos hace más fácil escribir código asíncrono es legible, la clase es muy potente, que contiene más de 50 métodos. . .

¿Cuál es CompletableFuture

CompletableFuturediseño de la clase se inspiró en Google Guavala ListenableFuture clase que implementa Futurey CompletionStagela interfaz y se añade un número de métodos, que apoya lambda, a través del uso del método de no bloqueo de devolución de llamada para mejorar el modelo de programación asíncrona. Que nos permite pasar en el hilo principal de la aplicación con un hilo diferente (es decir asíncrona) para ejecutar la tarea, y el progreso de la realización principal tarea de notificación de rosca o el fracaso de escribir código no bloqueante.

¿Por qué adoptar CompletableFuture

JavaLos 1,5 introduce liberación Future, se puede poner simplemente entendida como el resultado de la operación de un marcador de posición, que proporciona dos métodos para obtener el resultado de la operación.

  • get()Este método se llama hilo esperará indefinidamente a que el resultado de la operación.
  • get(long timeout, TimeUnit unit)Este método se llama hilo sólo en un momento determinado timeoutpara esperar los resultados dentro, si el tiempo de espera de espera arrojará TimeoutExceptionuna excepción.

FutureQue se puede utilizar Runnableo Callableinstancia presentada a la tarea, su código fuente puede ser visto, tal como existe varios problemas:

  • El bloqueo de llamada de get()método se bloqueará hasta que esperar hasta que se finalice el cálculo, no proporciona ningún método puede ser notificado de la finalización, sino que también no tiene la función de llamada de retorno adicional.
  • llamadas encadenadas proceso de polimerización y los resultados , en muchos casos nos gustaría vincular más Futurepara completar los largos cálculos, esta necesidad de tiempo para consolidar los resultados y enviar los resultados a otra tarea, la interfaz es difícil de completar este proceso.
  • El manejo de excepciones Future no proporciona ninguna forma de control de excepciones.

Estos problemas CompletableFutureya están resueltos, Echemos un vistazo a cómo utilizar CompletableFuture.

Cómo crear CompletableFuture

La forma más fácil es llamar al crear CompletableFuture.completedFuture(U value)método para obtener una completado CompletableFutureobjeto.

@Test
public void testSimpleCompletableFuture() {
    CompletableFuture<String> completableFuture = CompletableFuture.completedFuture("Hello mghio");
    assertTrue(completableFuture.isDone());
    try {
        assertEquals("Hello mghio", completableFuture.get());
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
}

Tenga en cuenta que si no completamos la CompleteableFuturellamada get, será porque el método Futureno es completa, por lo que la getllamada se bloqueará para siempre, entonces usted puede utilizar CompletableFuture.completeel método manual para completar Future.

tareas de procesamiento asíncrono

Cuando queremos hacer frente a los resultados del programa para realizar las tareas de forma asíncrona en segundo plano sin preocupación por la tarea, se puede utilizar el runAsyncmétodo, que recibe un Runnableparámetro de retorno tipo CompletableFuture<Void>.

@Test
public void testCompletableFutureRunAsync() {
    AtomicInteger variable = new AtomicInteger(0);
    CompletableFuture<Void> runAsync = CompletableFuture.runAsync(() -> process(variable));
    runAsync.join();
    assertEquals(100, variable.get());
}

public void process(AtomicInteger variable) {
    System.out.println(Thread.currentThread() + " Process...");
    variable.set(100);
}

Si queremos realizar tareas en segundo plano y procesamiento asíncrono resultados que tenga que hacer el trabajo, puede utilizar supplyAsyncel método, el método recibe un Supplier<T>tipo de un parámetro de retorno CompletableFuture<T>.

@Test
public void testCompletableFutureSupplyAsync() {
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
} 

Vea aquí es posible que tenga un problema, lleve a cabo anteriormente runAsyncy supplyAsynctarea roscado está por venir, quién lo creó? En realidad, es 8 y Java parallelStreamsimilares, CompletableFuturesino también de lo global ForkJoinPool.commonPool()para llevar a cabo estas tareas hilo de obtener. Mientras tanto, los dos métodos anteriores también proporcionan un grupo de subprocesos personalizado para realizar la tarea, de hecho, si usted va a averiguar sobre CompletableFutureel código fuente, se encuentra que APItodos los métodos tienen versiones sobrecargadas, con o sin la costumbre Executorde ejecución dispositivo.

@Test
public void testCompletableFutureSupplyAsyncWithExecutor() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    CompletableFuture<String> supplyAsync = CompletableFuture.supplyAsync(this::process, newFixedThreadPool);
    try {
        assertEquals("Hello mghio", supplyAsync.get()); // Blocking
    } catch (ExecutionException | InterruptedException e) {
        e.printStackTrace();
    }
}

public String process() {
    return "Hello mghio";
}

polimerización en cadena llamadas de proceso y resultados

Sabemos que CompletableFutureel get()método se mantendrá 阻塞hasta llegar a los resultados, CompletableFutureproporcionando thenApply, thenAccepty thenRunotros métodos para evitar esta situación, pero también puede agregar una notificación de devolución de llamada después de que se complete la tarea. Estos escenario de uso métodos es la siguiente:

  • thenApply cuando si vamos a partir de Futurecuando se ejecuta el negocio después de recibir el valor de código personalizado antes de la tarea, y luego regrese algún valor para esta tarea, puede utilizar este método
  • thenAccept Si queremos de Futuretiempo después de recibir un número de código de valor de negocio para ejecutar tareas personalizadas antes de que los resultados no se preocupan por el valor de retorno, puede utilizar este método
  • thenRun Si queremos código de negocio en el futuro finalizado la ejecución personalizada, y no queremos hacer esto cualquier valor de retorno, puede utilizar este método
@Test
public void testCompletableFutureThenApply() {
    Integer notificationId = CompletableFuture.supplyAsync(this::thenApplyProcess)
        .thenApply(this::thenApplyNotify) // Non Blocking
        .join();
    assertEquals(new Integer(1), notificationId);
}

@Test
public void testCompletableFutureThenAccept() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenAccept(this::thenAcceptNotify) // Non Blocking
        .join();
    assertEquals(100, variable.get());
}

@Test
public void testCompletableFutureThenRun() {
    CompletableFuture.supplyAsync(this::processVariable)
        .thenRun(this::thenRunNotify)
        .join();
    assertEquals(100, variable.get());
}

private String processVariable() {
    variable.set(100);
    return "success";
}

private void thenRunNotify() {
    System.out.println("thenRun completed notify ....");
}

private Integer thenApplyNotify(Integer integer) {
    return integer;
}

private void thenAcceptNotify(String s) {
    System.out.println(
    String.format("Thread %s completed notify ....", Thread.currentThread().getName()));
}

public Integer thenApplyProcess() {
    return 1;
}

Si un gran número de computación asíncrona, por lo que podemos seguir ofreciendo valor a una devolución de llamada desde la devolución de llamada a otra, es decir, utilizando un modo de llamada encadenada, utiliza muy simple.

@Test
public void testCompletableFutureThenApplyAccept() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .thenAccept((i) -> notifyByEmail()).join();
}

private void notifyByEmail() {
    // business code
    System.out.println("send notify by email ...");
}

private Double notifyBalance(Double d) {
    // business code
    System.out.println(String.format("your balance is $%s", d));
    return 9527D;
}

private Double calculateBalance(Object o) {
    // business code
    return 9527D;
}

private Double findAccountNumber() {
    // business code
    return 9527D;
}

amigos cuidadosas comparar pueden haber dado cuenta de que en todos los ejemplos anteriores de varios métodos, todos los métodos se ejecutan en el mismo hilo. Si queremos que estas tareas se ejecuten en un hilo separado, entonces podemos utilizar estos métodos correspondientes a la versión asíncrona.

@Test
public void testCompletableFutureApplyAsync() {
    ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
    ScheduledExecutorService newSingleThreadScheduledExecutor = Executors
        .newSingleThreadScheduledExecutor();
    CompletableFuture<Double> completableFuture =
        CompletableFuture
            .supplyAsync(this::findAccountNumber,
                newFixedThreadPool) // 从线程池 newFixedThreadPool 获取线程执行任务
            .thenApplyAsync(this::calculateBalance,
                newSingleThreadScheduledExecutor)
            .thenApplyAsync(this::notifyBalance);
    Double balance = completableFuture.join();
    assertEquals(9527D, balance);
}

Los resultados del proceso de implementación

thenComposeMétodos adecuados para el tratamiento de la dependencia de tareas, como por ejemplo un saldo de cuenta de la informática empresarial: En primer lugar, primero tenemos que encontrar una cuenta, y luego calcular el saldo de la cuenta, entonces el cálculo se ha completado antes de enviar la notificación. Todas estas tareas son dependientes en la declaración de empleos anteriores CompletableFutureresultados, entonces tenemos que utilizar el thenComposemétodo, de hecho, algo similar a Java 8 flujo de flatMapoperaciones.

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
    assertEquals(9527D, balance);
}

private CompletableFuture<Double> doSendNotifyBalance(Double aDouble) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doSendNotifyBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doCalculateBalance(Double d) {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doCalculateBalance ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private CompletableFuture<Double> doFindAccountNumber() {
    sleepSeconds(2);
    // business code
    System.out.println(String.format("%s doFindAccountNumber ....", Thread.currentThread().getName()));
    return CompletableFuture.completedFuture(9527D);
}

private void sleepSeconds(int timeout) {
    try {
        TimeUnit.SECONDS.sleep(timeout);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

thenCombineEl método se utiliza principalmente para combinar los resultados del procesamiento de una pluralidad de tareas separadas. Supongamos que tenemos que encontrar el nombre y dirección de una persona, se pueden utilizar diferentes tareas para obtener, respectivamente, y luego obtener los resultados que desea obtener información completa sobre la persona (nombre + dirección), es necesario combinar los dos enfoques, entonces podemos utilizar el thenCombinemétodo .

@Test
public void testCompletableFutureThenCombine() {
    CompletableFuture<String> thenCombine = this.findName().thenCombine(this.findAddress(), (name, address) -> name + address);
    String personInfo = thenCombine.join();
    assertEquals("mghio Shanghai, China", personInfo);
}

private CompletableFuture<String> findAddress() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "Shanghai, China";
    });
}

private CompletableFuture<String> findName() {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio ";
    });
}

Espere a que la ejecución de tareas múltiples completas

En muchos casos, queremos ejecutar varias tareas en paralelo, y luego realizar algún procesamiento de todas las tareas completadas. Supongamos que queremos encontrar tres nombre de usuario diferente y combinar los resultados. En este punto se puede utilizar el CompletableFuturemétodo estático allOf, que espera el método hasta que se hayan completado todas las tareas, debe tenerse en cuenta que este método no devuelve todas las tareas que se combinan los resultados, por lo que los resultados que necesitamos una combinación de tareas manuales.

@Test
public void testCompletableFutureAllof() {
    List<CompletableFuture<String>> list = Lists.newArrayListWithCapacity(4);
    IntStream.range(0, 3).forEach(num -> list.add(findName(num)));

    CompletableFuture<Void> allFuture = CompletableFuture
        .allOf(list.toArray(new CompletableFuture[0]));

    CompletableFuture<List<String>> allFutureList = allFuture
        .thenApply(val -> list.stream().map(CompletableFuture::join).collect(Collectors.toList()));

    CompletableFuture<String> futureHavingAllValues = allFutureList
        .thenApply(fn -> String.join("", fn));

    String result = futureHavingAllValues.join();
    assertEquals("mghio0mghio1mghio2", result);
}

private CompletableFuture<String> findName(int num) {
    return CompletableFuture.supplyAsync(() -> {
        sleepSeconds(2);
        // business code
        return "mghio" + num;
    });
} 

El manejo de excepciones

De hecho, una excepción no es un reparto bien en un programa de multiproceso, pero afortunadamente en CompletableFuturelos EE.UU. provisto de una forma muy conveniente de manejo de excepciones, en nuestro ejemplo anterior código:

@Test
public void testCompletableFutureThenCompose() {
    Double balance = this.doFindAccountNumber()
        .thenCompose(this::doCalculateBalance)
        .thenCompose(this::doSendNotifyBalance).join();
}

En el código anterior, los tres métodos doFindAccountNumber, doCalculateBalancey doSendNotifyBalancesiempre y cuando se produce alguna anormalidad, no se ejecutará la siguiente llamada al método.
CompletableFutureQue proporciona tres maneras de manejar excepciones, respectivamente exceptionally, handley los whenCompletemétodos. La primera forma es utilizar el exceptionallymétodo para manejar excepciones, si el método anterior falla y se produce una excepción, la devolución de llamada se llama excepción.

@Test
public void testCompletableFutureExceptionally() {
    CompletableFuture<Double> thenApply = CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .exceptionally(ex -> {
            System.out.println("Exception " + ex.getMessage());
            return 0D;
        });
    Double join = thenApply.join();
    assertEquals(9527D, join);
}

La segunda forma es usar un handleexcepciones mango método, y la forma de excepciones de manejar que lo anterior exceptionallyes la forma más flexible, podemos llegar a tanto el objeto de excepción actual y de procesamiento de resultados.

@Test
public void testCompletableFutureHandle() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .handle((ok, ex) -> {
            System.out.println("最终要运行的代码...");
            if (ok != null) {
            System.out.println("No Exception !!");
            } else {
            System.out.println("Exception " + ex.getMessage());
            return -1D;
            }
            return ok;
        });
}

Una tercera es utilizar un whenCompletemétodo de tratamiento de anormalidades.

@Test
public void testCompletableFutureWhenComplete() {
    CompletableFuture.supplyAsync(this::findAccountNumber)
        .thenApply(this::calculateBalance)
        .thenApply(this::notifyBalance)
        .whenComplete((result, ex) -> {
            System.out.println("result = " + result + ", ex = " + ex);
            System.out.println("最终要运行的代码...");
        });
}

resumen

En este trabajo, presentamos la CompletableFuturesiguiente parte de los métodos de la clase de método y el uso de esta clase mucho tiempo que proporciona la funcionalidad es muy potente, un mayor uso de la programación asincrónica, familiarizado con el uso básico de entender o fuente en profundidad análisis e implementación principios.

Supongo que te gusta

Origin www.cnblogs.com/mghio/p/12650256.html
Recomendado
Clasificación