Programación concurrente: Future y CompletableFuture

Introducción al futuro

Las formas más utilizadas para crear subprocesos en Java son Thread y Runnable. Si necesita que la tarea procesada actualmente devuelva resultados, debe utilizar Callable. Callable necesita cooperar con Future para ejecutarse.

Future es una interfaz y la clase de implementación FutureTask generalmente se usa para recibir los resultados de retorno de la tarea invocable.

Uso de tareas futuras

El siguiente ejemplo utiliza FutureTask para realizar una tarea asincrónica que devuelve resultados. Callable es la tarea que se ejecutará y FutureTask es la ubicación donde se almacenan los resultados devueltos por la tarea.

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    FutureTask<Integer> futureTask = new FutureTask<>(() -> {
    
    
        System.out.println("任务执行");
        Thread.sleep(2000);
        return 123+764;
    });

    Thread t = new Thread(futureTask);
    t.start();

    System.out.println("main线程启动了t线程处理任务");
    Integer result = futureTask.get();
    System.out.println(result);
}

Análisis de tareas futuras

Primero, echemos un vistazo a las propiedades principales de FutureTask.

/**
 * NEW -> COMPLETING -> NORMAL          任务正常执行,返回结果是正常的结果
 * NEW -> COMPLETING -> EXCEPTIONAL     任务正常执行,但是返回结果是异常
 * NEW -> CANCELLED              任务直接被取消的流程
 * NEW -> INTERRUPTING -> INTERRUPTED
 */
// 代表当前任务的状态
private volatile int state;
private static final int NEW          = 0;  // 任务的初始化状态
private static final int COMPLETING   = 1;  // Callable的结果(正常结果,异常结果)正在封装给当前的FutureTask
private static final int NORMAL       = 2;  // NORMAL任务正常结束
private static final int EXCEPTIONAL  = 3;  // 执行任务时,发生了异常
private static final int CANCELLED    = 4;  // 任务被取消了。
private static final int INTERRUPTING = 5;  // 线程的中断状态,被设置为了true(现在还在运行)
private static final int INTERRUPTED  = 6;  // 线程被中断了。

// 当前要执行的任务
private Callable<V> callable;
// 存放任务返回结果的属性,也就是futureTask.get需要获取的结果
private Object outcome; 
// 执行任务的线程。
private volatile Thread runner;
// 单向链表,存放通过get方法挂起等待的线程
private volatile WaitNode waiters;

Después de t.start, el método de llamada de Callable se ejecuta a través del método de ejecución, que es sincrónico y luego el resultado devuelto se asigna al resultado.

// run方法的执行流程,最终会执行Callable的call方法
public void run() {
    
    
    // 保证任务的状态是NEW才可以运行
    // 基于CAS的方式,将当前线程设置为runner。
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,null, Thread.currentThread()))
        return;
    // 准备执行任务
    try {
    
    
        // 要执行任务 c
        Callable<V> c = callable;
        // 任务不为null,并且任务的状态还处于NEW
        if (c != null && state == NEW) {
    
    
            // 放返回结果
            V result;
            // 任务执行是否为正常结束
            boolean ran;
            try {
    
    
                // 运行call方法,拿到返回结果封装到result中
                result = c.call();
                // 正常返回,ran设置为true
                ran = true;
            } catch (Throwable ex) {
    
    
                // 结果为null
                result = null;
                // 异常返回,ran设置为false
                ran = false;
                // 设置异常信息
                setException(ex);
            }
            if (ran)
                // 正常执行结束,设置返回结果
                set(result);
        }
    } finally {
    
    
        // 将执行任务的runner设置空
        runner = null;
        // 拿到状态
        int s = state;
        // 中断要做一些后续处理
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}


// 设置返回结果
protected void set(V v) {
    
    
    // 首先要将任务状态从NEW设置为COMPLETING
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    
    
        // 将返回结果设置给outcome。
        outcome = v;
        // 将状态修改为NORMAL,代表正常技术
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
        finishCompletion();
    }
}

Cuando el método get obtiene el resultado devuelto, verificará el estado actual del hilo. Si no se ha alcanzado el estado, es decir, el método de llamada no se ha ejecutado y el método set no se ha ejecutado, el hilo se suspenderá y bloqueado LockSupport.park (esto);.

public V get() throws InterruptedException, ExecutionException {
    
    
    // 拿状态
    int s = state;
    // 满足找个状态就代表现在可能还没有返回结果
    if (s <= COMPLETING)
        // 尝试挂起线程,等待拿结果
        s = awaitDone(false, 0L);
    return report(s);
}

// 线程要等待任务执行结束,等待任务执行的状态变为大于COMPLETING状态
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
    
    
    // 计算deadline,如果是get(),就是0,  如果是get(time,unit)那就追加当前系统时间
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    // 构建WaitNode
    WaitNode q = null;
    // queued = false
    boolean queued = false;
    // 死循环
    for (;;) {
    
    
        // 找个get的线程是否中断了。
        if (Thread.interrupted()) {
    
    
            // 将当前节点从waiters中移除。
            removeWaiter(q);
            // 并且抛出中断异常
            throw new InterruptedException();
        }

        // 拿到现在任务的状态
        int s = state;
        // 判断任务是否已经执行结束了
        if (s > COMPLETING) {
    
    
            // 如果设置过WaitNode,直接移除WaitNode的线程
            if (q != null)
                q.thread = null;
            // 返回当前任务的状态
            return s;
        }
        // 如果任务的状态处于 COMPLETING ,
        else if (s == COMPLETING)
            // COMPLETING的持续时间非常短,只需要做一手现成的让步即可。
            Thread.yield();

        // 现在线程的状态是NEW,(call方法可能还没执行完呢,准备挂起线程)
        else if (q == null)
            // 封装WaitNode存放当前线程
            q = new WaitNode();
        else if (!queued)
            // 如果WaitNode还没有排在waiters中,现在就排进来(头插法的效果)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
        else if (timed) {
    
    
            // get(time,unit)挂起线程的方式
            // 计算挂起时间
            nanos = deadline - System.nanoTime();
            // 挂起的时间,是否小于等于0
            if (nanos <= 0L) {
    
    
                // 移除waiters中的当前Node
                removeWaiter(q);
                // 返回任务状态
                return state;
            }
            // 正常指定挂起时间即可。(线程挂起)
            LockSupport.parkNanos(this, nanos);
        }
        else {
    
    
            // get()挂起线程的方式
            LockSupport.park(this);
        }
    }
}

Cuando se ejecuta la tarea (se ejecuta el método establecido), el hilo se activa mediante FinishCompletion, LockSupport.unpark (t);

// 任务状态已经变为了NORMAL,做一些后续处理
private void finishCompletion() {
    
    
    for (WaitNode q; (q = waiters) != null;) {
    
    
        // 拿到第一个节点后,直接用CAS的方式,将其设置为null
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
    
    
            for (;;) {
    
    
                // 基于q拿到线程信息
                Thread t = q.thread;
                // 线程不为null
                if (t != null) {
    
    
                    // 将WaitNode的thread设置为null
                    q.thread = null;
                    // 唤醒这个线程
                    LockSupport.unpark(t);
                }
                // 往后遍历,接着唤醒
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null;
                // 指向next的WaitNode
                q = next;
            }
            break;
        }
    }

    // 扩展方法,没任何实现,你可以自己实现
    done();

    // 任务处理完了,可以拜拜了!
    callable = null;   
}

Procesamiento de obtención de resultados devueltos

// 任务结束。
private V report(int s) throws ExecutionException {
    
    
    // 拿到结果
    Object x = outcome;
    // 判断是正常返回结束
    if (s == NORMAL)
        // 返回结果
        return (V)x;
    // 任务状态是大于取消
    if (s >= CANCELLED)
        // 甩异常。
        throw new CancellationException();
    // 扔异常。
    throw new ExecutionException((Throwable)x);
}

// 正常返回 report
// 异常返回 report
// 取消任务 report
// 中断任务 awaitDone

Futuro Completable

Problemas con FutureTask:
Problema 1: antes de que FutureTask obtenga el resultado de la ejecución del subproceso, el subproceso principal debe bloquear el método get y esperar a que el subproceso secundario termine de ejecutar el método de llamada antes de poder obtener el resultado devuelto.
Pregunta 2: Si no suspende el hilo a través de get, puede usar un bucle while para determinar constantemente si el estado de ejecución de la tarea ha finalizado y luego obtener el resultado una vez finalizado. Si la tarea no se ejecuta durante mucho tiempo, la CPU seguirá programando métodos para verificar el estado de la tarea, lo que desperdiciará recursos de la CPU.

FutureTask es una forma sincrónica y sin bloqueo de procesar tareas. Se necesita una forma asincrónica y sin bloqueos de procesar tareas. CompletableFuture proporciona varias soluciones de procesamiento asincrónicas y sin bloqueo hasta cierto punto.

CompletableFuture también implementa las funciones implementadas por la interfaz Future. Puede usar CompletableFuture directamente sin usar FutureTask y proporciona una función muy rica para realizar varias operaciones asincrónicas.

Aplicación de CompletableFuture

Lo más importante de CompletableFuture es que resuelve el problema de las devoluciones de llamadas asincrónicas.

CompletableFuture consiste en ejecutar una tarea asincrónica. La tarea asincrónica puede devolver un resultado o no. Utiliza las tres interfaces más centrales de la programación funcional.

Supplier - 生产者,没有入参,但是有返回结果
Consumer - 消费者,有入参,但是没有返回结果
Function - 函数,有入参,并且有返回结果

Proporciona dos métodos básicos para la operación básica.

supplyAsync(Supplier<U> supplier) 异步执行任务,有返回结果
runAsync(Runnable runnable) 异步执行任务,没有返回结果

Sin especificar un grupo de subprocesos, estas dos tareas asincrónicas se entregan a ForkJoinPool para su ejecución.

Pero utilizando solo estos dos métodos, no se puede lograr una devolución de llamada asincrónica. Si necesita continuar realizando operaciones de tareas posteriores con los resultados devueltos después de ejecutar la tarea actual, debe implementarla en función de otros métodos.

thenApply(Function<prevResult,currResult>); 等待前一个任务处理结束后,拿着前置任务的返回结果,再做处理,并且返回当前结果
thenApplyAsync(Function<prevResult,currResult>,线程池) 采用全新的线程执行
thenAccept(Consumer<preResult>);等待前一个任务处理结束后,拿着前置任务的返回结果再做处理,没有返回结果
thenAcceptAsync(Consumer<preResult>,线程池);采用全新的线程执行
thenRun(Runnable) 等待前一个任务处理结束后,再做处理。不接收前置任务结果,也不返回结果
thenRunAsync(Runnable[,线程池]) 采用全新的线程执行

En segundo lugar, es posible realizar un procesamiento relativamente complejo, ejecutando tareas posteriores mientras se ejecuta la tarea anterior. Espere a que se completen las tareas previas y posteriores antes de ejecutar la tarea final

thenCombine(CompletionStage,Function<prevResult,nextResult,afterResult>) 让prevResult和nextResult一起执行,等待执行完成后,获取前两个任务的结果执行最终处理,最终处理也可以返回结果
thenCombineAsync(CompletionStage,Function<prevResult,nextResult,afterResult>[,线程池]) 采用全新的线程执行
thenAcceptBoth(CompletionStage,Consumer<prevResult,nextResult>);让前置任务和后续任务同时执行,都执行完毕后,拿到两个任务的结果,再做后续处理,但是没有返回结果
thenAcceptBothAsync(CompletionStage,Consumer<prevResult,nextResult>[,线程池])采用全新的线程执行
runAfterBoth(CompletionStage,Runnble) 让前置任务和后续任务同时执行,都执行完毕后再做后续处理
runAfterBothAsync(CompletionStage,Runnble[,线程池]) 采用全新的线程执行

También brinda la posibilidad de ejecutar dos tareas al mismo tiempo, pero cuando finaliza una tarea y se devuelve el resultado, se realiza el procesamiento final.

applyToEither(CompletionStage,Function<firstResult,afterResult>) 前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理,再返回结果
acceptEither(CompletionStage,Consumer<firstResult>) 前面两个任务同时执行,有一个任务执行完,获取返回结果,做最终处理,没有返回结果
runAfterEither(CompletionStage,Runnable) 前面两个任务同时执行,有一个任务执行完,做最终处理

También proporciona la opción de esperar hasta que se procese la tarea de preprocesamiento y luego realizar el procesamiento posterior. El resultado devuelto por el procesamiento posterior es CompletionStage.

thenCompose(Function<prevResult,CompletionStage>)

Finalmente, existen varias posturas para manejar excepciones.

exceptionally(Function<Throwable,currResult>)  
whenComplete(Consumer<prevResult,Throwable>) 
hanle(Function<prevResult,Throwable,currResult>)

Ejemplo de CompletableFuture

public static void main(String[] args) throws InterruptedException {
    
    
    sout("我回家吃饭");

    CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
    
    
        sout("阿姨做饭!");
        return "锅包肉!";
    });
    sout("我看电视!");

    sout("我吃饭:" + task.join());
}
public static void main(String[] args) throws InterruptedException {
    
    
    sout("我回家吃饭");

    CompletableFuture<String> task = CompletableFuture.supplyAsync(() -> {
    
    
        sout("阿姨炒菜!");
        return "锅包肉!";
    },executor).thenCombineAsync(CompletableFuture.supplyAsync(() -> {
    
    
        sout("小王焖饭");
        return "大米饭!";
    },executor),(food,rice) -> {
    
    
        sout("大王端" + food + "," + rice);
        return "饭菜好了!";
    },executor);

    sout("我看电视!");
    sout("我吃饭:" + task.join());
}

Resumir

Future y CompletableFuture son interfaces y clases que se utilizan para manejar tareas asincrónicas. Sus principales diferencias radican en la complejidad funcional y los escenarios de uso. Future es relativamente simple y se utiliza principalmente para el procesamiento de tareas asincrónicas simples, mientras que CompletableFuture es más flexible y potente y adecuado para escenarios complejos de procesamiento de tareas asincrónicas.

Supongo que te gusta

Origin blog.csdn.net/qq_28314431/article/details/133134758
Recomendado
Clasificación