Programação simultânea - Futuro e Futuro Completável

Introdução ao Futuro

As formas comumente usadas para criar threads em Java são Thread e Runnable. Se precisar que a tarefa atualmente processada retorne resultados, você precisará usar Callable. Callable precisa cooperar com o Future para funcionar.

Future é uma interface e a classe de implementação FutureTask geralmente é usada para receber os resultados de retorno da tarefa Callable.

Uso da FutureTask

O exemplo a seguir usa FutureTask para executar uma tarefa assíncrona que retorna resultados. Callable é a tarefa a ser executada e FutureTask é o local onde os resultados retornados pela tarefa são armazenados.

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álise FutureTask

Primeiro, vamos dar uma olhada nas propriedades principais do 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;

Após t.start, o método call de Callable é executado através do método run.Este método é síncrono e, em seguida, o resultado de retorno é atribuído ao 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();
    }
}

Quando o método get obtiver o resultado de retorno, ele verificará o status atual do thread.Se o status não for atingido, ou seja, o método de chamada não foi executado e o método set não foi executado, o thread será suspenso e bloqueou LockSupport.park(isto);.

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

Quando a tarefa é executada (o método set é executado), o thread é despertado por 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;   
}

Processamento de obtenção de resultados retornados

// 任务结束。
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

CompletávelFuturo

Problemas com FutureTask:
Problema 1: Antes que FutureTask obtenha o resultado da execução do thread, o thread principal precisa bloquear o método get e esperar que o thread filho termine de executar o método de chamada antes de obter o resultado de retorno.
Pergunta 2: Se você não suspender o thread por meio de get, poderá usar um loop while para determinar constantemente se o status de execução da tarefa terminou e, em seguida, obter o resultado após o término. Se a tarefa não for executada por um longo período, a CPU continuará agendando métodos para verificar o status da tarefa, o que desperdiçará recursos da CPU.

FutureTask é uma forma síncrona e sem bloqueio de processar tarefas. É necessária uma forma assíncrona e sem bloqueio de processamento de tarefas. CompletableFuture fornece várias soluções de processamento assíncrono e sem bloqueio até certo ponto.

CompletableFuture também implementa as funções implementadas pela interface Future. Você pode usar CompletableFuture diretamente sem usar FutureTask. Ele fornece uma função muito rica para executar várias operações assíncronas.

Aplicação de CompletableFuture

A coisa mais importante sobre CompletableFuture é que ele resolve o problema de retornos de chamada assíncronos

CompletableFuture é executar uma tarefa assíncrona. A tarefa assíncrona pode retornar um resultado ou não. Ela usa as três interfaces mais básicas da programação funcional.

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

Fornece dois métodos básicos para operação básica

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

Sem especificar um pool de threads, essas duas tarefas assíncronas são entregues ao ForkJoinPool para execução.

Mas usando apenas esses dois métodos, o retorno de chamada assíncrono não pode ser alcançado. Se precisar continuar a executar operações de tarefa subsequentes com os resultados retornados após a execução da tarefa atual, será necessário implementá-la com base em outros métodos.

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

Em segundo lugar, é possível realizar processamentos relativamente complexos, executando tarefas subsequentes enquanto a tarefa anterior está sendo executada. Aguarde a conclusão das pré e pós-tarefas antes de executar a tarefa 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[,线程池]) 采用全新的线程执行

Também oferece a possibilidade de executar duas tarefas ao mesmo tempo, mas quando uma tarefa termina e o resultado é retornado, o processamento final é feito.

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

Ele também oferece a opção de aguardar até que a tarefa de pré-processamento seja processada e, em seguida, fazer o processamento subsequente. O resultado retornado pelo processamento subsequente é CompletionStage.

thenCompose(Function<prevResult,CompletionStage>)

Finalmente, existem várias posturas para lidar com exceções

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

Exemplo 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 e CompletableFuture são interfaces e classes usadas para lidar com tarefas assíncronas. Suas principais diferenças residem na complexidade funcional e nos cenários de uso. Future é relativamente simples e é usado principalmente para processamento de tarefas assíncronas simples, enquanto CompletableFuture é mais flexível e poderoso e adequado para cenários complexos de processamento de tarefas assíncronas.

Acho que você gosta

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