Combate de proyectos: use CompletableFuture de JUC para realizar tareas y realizar devoluciones de llamada asincrónicas basadas en valores de retorno

prefacio

Bajo la coerción de la implementación, el programador Daji tiene nuevas necesidades, y la traducción al lenguaje técnico es más o menos así:

Sigue llamando a una interfaz remota (miles de veces). La interfaz devolverá una cadena de identificadores y volverá a escribir en nuestra base de datos local con este identificador.

El tiempo de respuesta de esta interfaz remota es muy largo, de 1 a 3 segundos. Una vez que la interfaz devuelve una cadena de ID y vuelve a escribir los ID en la base de datos local, el proceso es relativamente corto, solo alrededor de 0,05 s.

Así que pensé en encapsular la interfaz remota de consulta en una función, volver a escribir en la base de datos local en una función y encapsular el manejo de excepciones en una función. Esto permite un desacoplamiento máximo.

En el caso de garantizar la eficiencia, se debe utilizar un grupo de subprocesos.Si se utiliza un grupo de subprocesos, hay dos formas de resolver este requisito:

  • Método 1: use el ExecutorService incorporado para crear un grupo de subprocesos y llame a su método de envío para abrir el subproceso. Ejecute en serie llamadas a interfaces remotas en un solo hilo -> reescriba el estado de la base de datos -> manejo de excepciones
  • Método 2: use CompletableFuture de JUC para crear un grupo de subprocesos, llame a su método supplyAsync para abrir un subproceso, solo ejecute el método de [llamar a la interfaz remota] en este subproceso y luego vuelva a llamar a la [base de datos de reescritura] encapsulada de acuerdo con el devuelve el resultado de la llamada al hilo.】método. Si el resultado devuelto es anómalo, se vuelve a llamar al método encapsulado [Control de excepciones].

El orden de ejecución de estos dos métodos es exactamente el mismo y la eficiencia es similar. Si considera la legibilidad del código, puede elegir el método 1. Sin embargo, el segundo método tiene más sentido lógico y, en el caso del desarrollo colaborativo de varias personas, la función de subproceso es más sencilla y el grado de acoplamiento es menor.

Finalmente, se eligió el método 2 como plan de desarrollo. Centrémonos en la implementación técnica de CompletableFuture.

Crear un grupo de subprocesos

Use CompletableFuture para abrir un subproceso, este método puede pasar en un grupo de subprocesos personalizado; de lo contrario, se usa el grupo de subprocesos predeterminado.

Cree un grupo de subprocesos con el siguiente código Java:

Varios parámetros pasados ​​por el constructor de creación de grupo de subprocesos se determinan de acuerdo con el volumen comercial. De acuerdo con la cantidad de servicios, ajuste los parámetros del grupo de subprocesos de la siguiente manera:

//定义线程池
    public static final ExecutorService threadPool =
            new ThreadPoolExecutor(
                    20,
                    50,
                    60L,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(50),
                    new ThreadPoolExecutor.AbortPolicy());

El número de subprocesos residentes se ajusta a 20 y el número máximo de subprocesos es de 50. Si excede, se adopta una estrategia de descarte.

hilo abierto

Úselo para CompletableFuture.supplyAsynciniciar un hilo. ¿Por qué usar supplyAsync este método? Porque el método puede obtener el valor de retorno.

Cuando CompletableFuture crea un subproceso, puede pasar un grupo de subprocesos personalizado, de lo contrario, utilizará el grupo de subprocesos predeterminado.Si el grupo de subprocesos se acaba de crear, pase el grupo de subprocesos recién creado.

El código es el siguiente (threadPool es el grupo de subprocesos que se acaba de crear):

//该方法可以传入自定义线程池,否则就用默认的线程池;
            CompletableFuture<Integer> thread = CompletableFuture.supplyAsync(
                    () -> callRemoteApi(), threadPool);

Solo hay una cosa en este hilo: llamar a la API remota.

Obtenga el valor de retorno del subproceso y llame a la base de datos de reescritura de acuerdo con el valor de retorno

Después de ejecutar el subproceso anterior, devolverá una identificación y luego consultaremos la base de datos de acuerdo con la identificación. El método específico utilizado es whenCompleteAsync:

//线程池执行完毕,如果成功了,则回写,如果失败了,就执行失败处理逻辑
            thread.whenCompleteAsync(
                    (result,throwable)-> {
    
    
                        if (result != -10086) //如果线程池返回了调用远程API成功的结果,那么就调用回写数据库的方法
                            writeBack(result);
                        else
                            exceptionHandle(result);//执行失败处理逻辑
                    }
            );

Este método es un método de devolución de llamada asíncrono, y el grupo de subprocesos devolverá la llamada a la función [reescritura de la base de datos] que definimos.

Finalmente, mire el método whenCompleteAsync en sí mismo:

El método no termina con Async, lo que significa que la Acción se ejecuta usando el mismo subproceso, mientras que Async se puede
ejecutar usando otros subprocesos (si se usa el mismo grupo de subprocesos, también puede ser seleccionado para su ejecución por el mismo subproceso)

Introducido aquí, el conocimiento teórico no debería ser un problema. Vea el código de demostración completo a continuación:

Código de demostración completo


import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.*;


/*
    该测试结果完全满足需求。
    下面代码的目标:使用线程池调用远程API,远程API会返回一串id
    远程API有一定概率返回失败,返回失败的状态码总是 -10086

    线程池接收API返回结果,如果成功,就将该id回写数据库
    如果失败,就调用异常处理逻辑。

    API参考资料:https://www.cnblogs.com/wuwuyong/p/15496841.html
*/
public class E06_CompletableFuture_AsyncCallback {
    
    

    //定义线程池
    //和实施讨论并且根据业务数量,将线程池的参数调整如下:有20个常驻线程。
    public static final ExecutorService threadPool =
            new ThreadPoolExecutor(
                    20,
                    50,
                    60L,
                    TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(50),
                    new ThreadPoolExecutor.AbortPolicy());


    public static void main(String[] args) {
    
    


        //一共需要发送15次远程请求,向数据库回写15次状态
        for (int i = 0; i < 15; i++) {
    
    

            //该方法可以传入自定义线程池,否则就用默认的线程池;
            CompletableFuture<Integer> thread = CompletableFuture.supplyAsync(
                    () -> callRemoteApi(), threadPool);

            //线程池执行完毕,如果成功了,则回写,如果失败了,就执行失败处理逻辑
            thread.whenCompleteAsync(
                    (result,throwable)-> {
    
    
                        if (result != -10086) //如果线程池返回了调用远程API成功的结果,那么就调用回写数据库的方法
                            writeBack(result);
                        else
                            exceptionHandle(result);//执行失败处理逻辑
                    }
            );

        }


    }
    //调用一个远程API,该API会返回一个id
    private static Integer callRemoteApi(){
    
    
        try {
    
    
            //模拟长任务:调用时间。该调用时间比较慢,长达三秒
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        Random random = new Random();
        int i = random.nextInt(10);
        if (judgeSuccess()) {
    
    
            System.out.println("已经调用远程API接口,此次调用成功,返回的id是:"+i);
            return i;
        } else {
    
    
            System.out.println("已经调用远程API接口,此次调用失败");
            return -10086;
        }
    }

    private static void writeBack(int id) {
    
    
        //回写状态到数据库是一个短任务(相对而言)
        System.out.println("已经将id为:" + id + "的单据,回写状态到数据库");
    }

    //如果远程调用失败了就进行异常处理。
    private static void exceptionHandle(int code){
    
    
        System.out.println("本次调用失败,已经调用失败处理逻辑。错误码为:"+code);
    }

    //调用远程接口有50%的概率失败。这个方法用于判断是否接口调用成功
    private static boolean judgeSuccess() {
    
    
        Random random = new Random();
        int i = random.nextInt(10);
        if (i <= 5) {
    
    
            return true;
        } else {
    
    
            return false;
        }
    }
}

posdata

Esta es solo una demostración simple. La parte más poderosa de CompletableFuture es que puede organizar, determinar el orden de ejecución de cada subproceso, esperar el resultado de la ejecución del subproceso, etc. Las operaciones complejas se pueden realizar utilizando varios subprocesos. Consulte https://www.cnblogs.com/wuwuyong/p/15496841.html para obtener detalles , que proporciona ejemplos más detallados.

Supongo que te gusta

Origin blog.csdn.net/weixin_44757863/article/details/122370050
Recomendado
Clasificación