Beispiele: Futurn, FutureTask, CompletionService, CompletableFuture

1. Abrufbar

In diesem Artikel [Thread] Grundkonzepte und Erstellungsmethoden von Threads (1) kennen wir verschiedene Möglichkeiten zum Erstellen von Threads. Zwei davon werden über Schnittstellen implementiert: Runnable, Callable. Ihre Unterschiede sind wie folgt:

  • RunnableDie Methoden in der Schnittstelle haben keinen Rückgabewert, Callabledie Methoden in der Schnittstelle haben jedoch Rückgabewerte.
  • CallableMethoden in Schnittstellen können Ausnahmen auslösen

So verwenden Sie Callabledie Schnittstelle

Es gibt zwei Möglichkeiten, Callabledie Schnittstelle zu nutzen:

  1. passieren Thread, wieder kombinieren Future,FutureTask
  2. Durch die Methoden submit(), invokeAll(), des Thread-PoolsinvokeAny()

Methode eins:

public static void main(String[] args) throws Exception {
    
    
    Callable<String> task = () -> "I am a callable Task";
    // 将 Callable 封装为 FutureTask
    FutureTask<String> futureTask = new FutureTask<>(task);
    new Thread(futureTask).start();
    String result = futureTask.get();
}

Hinweis: get()Die Methode ist eine Blockierungsmethode. Sie wird blockiert, bis das Ergebnis vorliegt!

Methode zwei:

public static void main(String[] args) throws Exception {
    
    
    Callable<String> task = () -> "I am a callable Task";
    ExecutorService threadPool = Executors.newFixedThreadPool(2);
    Future<String> future = threadPool.submit(task);
    String result = future.get();
    threadPool.shutdown();
}

2. Zukunft, FutureTask

Schauen Sie sich zunächst das Beziehungsdiagramm zwischen ihnen an:

Fügen Sie hier eine Bildbeschreibung ein

FutureSchnittstelle : Die Zukunft besteht darin, das Ausführungsergebnis einer bestimmten ausführbaren oder aufrufbaren Aufgabe abzubrechen, abzufragen, ob sie abgeschlossen ist, und das Ergebnis abzurufen. Bei Bedarf können Sie get()das Ausführungsergebnis über die Methode abrufen, die blockiert, bis die Aufgabe das Ergebnis zurückgibt.

public interface Future<V> {
    
    
    // 取消任务,如果任务正在运行的,mayInterruptIfRunning为true时,表明这个任务会被打断的,并返回true;
    // 为false时,会等待这个任务执行完,返回true;若任务还没执行,取消任务后返回true,如任务执行完,返回false
    boolean cancel(boolean mayInterruptIfRunning);
    // 判断任务是否被取消了,正常执行完不算被取消
    boolean isCancelled();
    // 判断任务是否已经执行完成,任务取消或发生异常也算是完成,返回true
    boolean isDone();
    // 获取任务返回结果,如果任务没有执行完成则等待完成将结果返回,如果获取的过程中发生异常就抛出异常,
    // 比如中断就会抛出InterruptedException异常等异常
    V get() throws InterruptedException, ExecutionException;
    // 在规定的时间如果没有返回结果就会抛出TimeoutException异常
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
}

FutureTaskSchnittstelle : FutureTaskImplementiert RunnableFuturedie Schnittstelle, und diese Schnittstelle erbt RunnableSchnittstelle und FutureSchnittstelle. Daher werden run()Methoden und FutureMethoden in Schnittstellen zwangsläufig erneuert.

public class FutureTask<V> implements RunnableFuture<V> {
    
    

	private Callable<V> callable;
	// 存储任务结果
	private Object outcome;
	
	public void run() {
    
    
        if (state != NEW ||!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
            return;
        try {
    
    
            Callable<V> c = callable;
            if (c != null && state == NEW) {
    
    
                V result = c.call();
                set(result);
            }
        } finally {
    
    
            //...
        }
    }
    
    protected void set(V v) {
    
    
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    
    
        	// 将结果赋值给 outcome
            outcome = v;
            // 修改线程状态
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
            // 移除所有等待线程并发出信号
            finishCompletion();
        }
    }
    
    private void finishCompletion() {
    
    
        for (WaitNode q; (q = waiters) != null;) {
    
    
            if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
    
    
                for (;;) {
    
    
                    Thread t = q.thread;
                    if (t != null) {
    
    
                        q.thread = null;
                        LockSupport.unpark(t);
                    }
                    WaitNode next = q.next;
                    if (next == null)
                        break;
                    q.next = null; // unlink to help gc
                    q = next;
                }
                break;
            }
        }
		// 留给子类重写
        done();
        callable = null;        // to reduce footprint
    }
}

FutureTask.run()Die Logik der Methode besteht darin, Callable.call()die Aufgabe durch Aufrufen der Methode auszuführen und das zurückgegebene Ergebnis set()über die Methode dem Attribut zuzuweisen outcome.

Dann ist der Prozess des obigen Codeblocks new Thread(futureTask).start();:

Die run()-Methode von FutureTask wird über die start()-Methode von Thread und schließlich über die call()-Methode von Callable ausgeführt!

get()Implementierung der Methode: get()Die Methode verwendet zunächst statedie Variable, um festzustellen, ob die Aufgabe abgeschlossen wurde: Wenn sie abgeschlossen ist, wird das Ergebnis direkt zurückgegeben. Andernfalls wird ein Warteknoten erstellt und in die Warteschlange zum Drehen geworfen , wodurch der Hauptthread blockiert wird. Nachdem der Thread auf der anderen Seite das Ergebnis berechnet hat, entfernt er alle Knoten in der Warteschlange und weckt den Thread auf.

public V get() throws InterruptedException, ExecutionException {
    
    
    int s = state;
    // 任务还未执行完,等待执行完
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
    // 任务执行完|已取消,返回结果或者抛出异常
    return report(s);
}

private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    
    
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
    
    
    	// 发生线程中断,从等待队列中移除节点,并抛出中断异常
        if (Thread.interrupted()) {
    
    
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
        // 任务已执行完|已取消
        if (s > COMPLETING) {
    
    
        	// 若已有等待节点,将线程置空,避免另外一边再次调用 unpark()
            if (q != null)
                q.thread = null;
            return s;
        }
        // 任务正在执行,让出时间片
        else if (s == COMPLETING)
            Thread.yield();
        // 如果还未构造等待队列节点,则构造
        else if (q == null)
            q = new WaitNode();
        // 如果为入队,则 CAS 尝试入队;如果入队失败,则下一次循环再次尝试
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                 q.next = waiters, q);
        // 通过 LockSupport.park() 阻塞线程
        else if (timed) {
    
    
            nanos = deadline - System.nanoTime();
            // 如果限时,且不大于 0,则从等待队列中移除节点,并返回
            if (nanos <= 0L) {
    
    
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        else
            LockSupport.park(this);
    }
}

private V report(int s) throws ExecutionException {
    
    
    Object x = outcome;
    if (s == NORMAL)
        return (V)x;
    //...
}

get()Die Logik der Methodenverarbeitung lautet wie folgt:

  1. Stellen Sie zunächst fest, ob der Statuswert größer als „comleting“ ist, d wird geworfen. Wenn es abnormal ist, wird die vom Ergebnis aufgezeichnete Ausnahme geworfen.
  2. Wenn der Statuswert angibt, dass die Ausführung noch nicht abgeschlossen ist, wird eine Spin-Operation gestartet, bei der es sich um eine Endlosschleife handelt.
  3. Wenn der Status abgeschlossen ist, geben Sie die Zeitscheibe über Thread.yield auf.
  4. Andernfalls, wenn das Ergebnis bereits vorliegt, kehren Sie zurück
  5. Andernfalls, wenn der Warteknoten noch nicht erstellt wurde, wird der Warteknoten erstellt und der aktuelle Thread wird im Knoten gespeichert.
  6. Andernfalls, wenn der Knoten noch nicht zur Warteschlange hinzugefügt wurde, fügen Sie den Knoten dem Team hinzu.
  7. Andernfalls LockSupport.park()blockiert der Aufruf der Methode den Thread

Nachdem der Thread, der die Aufgabe ausführt, die Ausführung der Aufgabe abgeschlossen hat (die Ausführung run()der Methode abgeschlossen hat),Speichern Sie einfach das Ergebnis oder die Ausnahme im Ergebnis, ändern Sie den Statuswert, entfernen Sie die Knoten in der Warteschlange nacheinander und rufen Sie die LockSupport.unpark()Methode auf, um den Thread im Knoten zu aktivieren(Diese Operationen sind in set()Methoden)

Fall: Verwenden Sie Future, um mehrere asynchrone Aufgaben stapelweise auszuführen und die Ergebnisse zu erhalten

ExecutorService executor = Executors.newFixedThreadPool(2); 
Future f1 = excutor.submit(c1);
f1.get();
Future f2 = excutor.submit(c2);
f2.get();

f1.get() Es wird blockiert, bevor die Erfassung erfolgreich ist, was die Ausführung von c2 blockiert und die Effizienz erheblich verringert.

FutureSchnittstellenbeschränkungen:

Im Wesentlichen stellt ein Future das Ergebnis einer asynchronen Berechnung dar. Es stellt isDone() bereit, um zu erkennen, ob die Berechnung abgeschlossen wurde. Nach Abschluss der Berechnung kann das Berechnungsergebnis über die Methode get() abgerufen werden. Es gibt jedoch viele Einschränkungen:

  1. Mehrere Aufgaben gleichzeitig ausführen : Future bietet nur die Methode get() zum Erhalten von Ergebnissen und blockiert
  2. Aufrufe können nicht an mehrere Aufgaben gekettet werden : Wenn Sie nach Abschluss der Berechnungsaufgabe eine bestimmte Aktion ausführen möchten, z. B. das Senden einer E-Mail, bietet Future eine solche Funktion nicht
  3. Unfähigkeit, mehrere Aufgaben zu kombinieren : Wenn Sie 10 Aufgaben ausführen und erwarten, nach deren Ausführung eine bestimmte Aktion auszuführen, können Sie in einer Zukunft nichts tun
  4. Keine Ausnahmebehandlung : Es gibt keine Methode zur Ausnahmebehandlung in der Future-Schnittstelle

3. Abschlussservice

Callable + FutureObwohl mehrere Aufgaben parallel ausgeführt werden können, müssen Sie, wenn die vorherige Aufgabe langsam ausgeführt wird, blockieren und warten, bis die vorherige Aufgabe die Ausführung der folgenden Aufgabe abgeschlossen hat, bevor das Ergebnis erhalten werden kann. Die Hauptfunktion von CompletionServiceist:Rufen Sie beim Generieren der Aufgabe den Rückgabewert der Aufgabe ab. Lassen Sie die beiden Dinge getrennt ausführen, damit sich die Aufgaben nicht gegenseitig blockieren. Die Ergebnisse, die zuerst ausgeführt werden, können zuerst abgerufen werden, und die Aufgabenreihenfolge ist nicht mehr abhängig.

ExecutorCompletionServiceDie Klasse implementiert CompletionServicedie Schnittstelle

Verwenden Sie CompletionService, um die oben genannten Fälle zu optimieren

public static void main(String[] args) throws InterruptedException, ExecutionException {
    
    
    ExecutorService executor = Executors.newFixedThreadPool(10);
    CompletionService<Integer> cs = new ExecutorCompletionService<>(executor);

    Future<Integer> f1 = cs.submit(() -> {
    
    
        Thread.sleep(2000);
        return 1;
    });
    Future<Integer> f2 = cs.submit(() -> {
    
    
        Thread.sleep(1000);
        return 2;
    });
    for (int i = 0; i < 2; i++) {
    
    
        Integer integer = cs.take().get();
        System.out.println(integer);
    }
}

CompletionServiceSchnittstelle:

public class ExecutorCompletionService<V> implements CompletionService<V> {
    
    
	private final Executor executor;
    private final AbstractExecutorService aes;
    // 已完成的任务队列
    private final BlockingQueue<Future<V>> completionQueue;
	
	public Future<V> submit(Callable<V> task) {
    
    
		// 将 Callable 转化为 FutureTask
        RunnableFuture<V> f = newTaskFor(task);
        // 会调用 FutureTask 类中的 run() 方法
        executor.execute(new QueueingFuture(f));
        return f;
    }
	
	// 将 Callable 转化为 FutureTask
	private RunnableFuture<V> newTaskFor(Callable<V> task) {
    
    
        if (aes == null)
            return new FutureTask<V>(task);
        else
            return aes.newTaskFor(task);
    }
	
	// 内部类:任务完成后,将已完成的任务添加到队列中
	private class QueueingFuture extends FutureTask<Void> {
    
    
        QueueingFuture(RunnableFuture<V> task) {
    
    
            super(task, null);
            this.task = task;
        }
        // done() 方法:FutureTask 中的 finishCompletion() 方法调用。这里,被子类重写
        protected void done() {
    
     completionQueue.add(task); }
        private final Future<V> task;
    }
}

Werfen wir noch einmal einen Blick darauf take()oder poll(): Es sind alles OperationencompletionQueue

public Future<V> take() throws InterruptedException {
    
    
    return completionQueue.take();
}

public Future<V> poll() {
    
    
    return completionQueue.poll();
}

public Future<V> poll(long timeout, TimeUnit unit)
        throws InterruptedException {
    
    
    return completionQueue.poll(timeout, unit);
}

4. Abschlussphase

CompletionStageEs handelt sich um eine neue Schnittstelle in Java 8, die für die Stufenverarbeitung bei der asynchronen Ausführung verwendet wird

Szenario: Eine große Aufgabe kann in mehrere Unteraufgaben aufgeteilt werden und es gibt eine offensichtliche Reihenfolge zwischen den Unteraufgaben oder eine Unteraufgabe hängt vom Abschlussergebnis einer anderen Unteraufgabe ab, dann ist CompletionStage eine gute Wahl

CompletionStage besteht darin, eine große Aufgabe in Teilaufgaben zu unterteilen. Diese Teilaufgaben bilden basierend auf bestimmten parallelen und seriellen Kombinationen verschiedene Phasen der Aufgabe.

Schauen Sie sich die Benutzeroberfläche an:

public interface CompletionStage<T> {
    
    

	public <U> CompletionStage<U> thenApply(Function<? super T,? extends U> fn);
	//...
	public CompletionStage<Void> runAfterBoth(CompletionStage<?> other, Runnable action);
	//...
	public <U> CompletionStage<U> applyToEither(CompletionStage<? extends T> other, Function<? super T, U> fn);
	//...
}

CompletionStageEs gibt viele Methoden in der Schnittstelle, aber es ist nicht schwer, Muster zu finden, denen man folgen kann: Sie bestehen im Wesentlichen aus Schlüsselwörtern wie then, apply, async, Accept, run, combine, Both, Each, After, Compose, When und and handhaben. Diese Schlüsselwörter können wie folgt verstanden werden:

  • dann: Gibt die Reihenfolge der Phasen an, dh eine Phase wartet auf den Abschluss einer anderen Phase
  • anwenden: Wie Funktion bedeutet es, einen Parameter zu verbrauchen und ein Ergebnis bereitzustellen
  • async: asynchrones Flag, das heißt, ob die Ausführung der Bühnenaufgabe relativ zum aktuellen Thread synchron oder asynchron ist
  • Akzeptieren: Identisch mit Consumer, was die Verwendung eines Parameters anzeigt
  • ausführen: weder verbrauchen noch löschen, gleiche Bedeutung wie die ausführbare Schnittstelle
  • Kombinieren: Kombinieren Sie die Ergebnisse zweier Phasen und geben Sie die neue Phase zurück
  • Both: Zeigt an, dass beide Bedingungen erfüllt sind, bevor andere Dinge ausgeführt werden.
  • Entweder: Zeigt an, dass eine der beiden Bedingungen erfüllt ist, bevor andere Dinge ausgeführt werden, die beiden entsprechen
  • Nachher: ​​Tabellensequenz, eine Aufgabe folgt nach der anderen, ähnlich wie damals
  • compose: bedeutet das Generieren neuer Ergebnisse basierend auf vorhandenen Ergebnissen, genau wie bei Function. Wenn Sie genau hinschauen, gibt es Unterschiede zwischen den Parametern von compose und thenApply. Die spezifischen Unterschiede werden unten aufgeführt.
  • wann: Entspricht whenComplete, die BiConsumer-Aktion wird ausgeführt, wenn die aktuelle Phase normal oder abnormal abgeschlossen wird.
  • Handle: Löst eine BiFunction-Aktion aus, nachdem die aktuelle Phase normal oder abnormal abgeschlossen wurde.

5. Abschließbare Zukunft

CompletableFutureImplementierung der Schnittstelle und Erstellung umfangreicher Erweiterungen auf dieser Basis, die die Einschränkungen von Futureperfekt ausgleichenFutureCompletableFutureCompletionStage

public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
    
    
	// 存储结果
	volatile Object result;
}

5.1 Gängige Methoden


Abhängigkeiten

  • thenApply(): Übergeben Sie das Ausführungsergebnis der vorherigen Aufgabe an die folgende Funktion
CompletableFuture.supplyAsync(() -> 100).thenApply(x -> x * 100);
  • thenCompose(): Wird zum Verbinden zweier abhängiger Aufgaben verwendet und das Ergebnis wird von der zweiten Aufgabe zurückgegeben
CompletableFuture.supplyAsync(() -> 100).thenCompose(x -> CompletableFuture.supplyAsync(() -> x * 100));

und Beziehung festlegen

  • thenCombine(): Aufgaben zusammenführen, mit Rückgabewert
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 100);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 200);
Integer result = future1.thenCombine(future2, Integer::sum).get();
  • thenAccepetBoth(): Nachdem die Ausführung der beiden Aufgaben abgeschlossen ist, werden die Ergebnisse ohne Rückgabewert zur Verarbeitung an thenAccepetBoth übergeben.
future1.thenAcceptBoth(future2, (x, y) -> System.out.println(x + y));
  • runAfterBoth(): Nachdem beide Aufgaben abgeschlossen sind, führen Sie den nächsten Vorgang aus (Aufgabe vom Typ „Ausführbar“). Alle werden synchron ausgeführt
future1.runAfterBoth(future2, () -> System.out.println(Thread.currentThread().getName()));

oder Aggregationsbeziehung

  • applyToEither(): Welche der beiden Aufgaben schneller ausgeführt wird, verwendet das Ergebnis und hat einen Rückgabewert.
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
    
    
    Thread.sleep(1000);
    return 100;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
    
    
    Thread.sleep(500);
    return 200;
});
System.out.println(future1.applyToEither(future2, x -> x * 100).get());
  • acceptEither(): Welche der beiden Aufgaben schneller ausgeführt wird, verbraucht das Ergebnis. Kein Rückgabewert.
  • runAfterEither(): Jede Aufgabe ist abgeschlossen. Fahren Sie mit dem nächsten Schritt fort (Aufgabe vom Typ „Ausführbar“).

Parallele Ausführung

  • allOf(): Wenn alle angegebenen CompletableFutures abgeschlossen sind, wird ein neues CompletableFuture zurückgegeben
  • anyOf(): Wenn ein gegebenes CompletablFuture abgeschlossen ist, wird ein neues CompletableFuture zurückgegeben

Asynchrone Operationen

  • runAsync()::
  • supplyAsync()::

Bei Verwendung einer Methode ohne Angabe eines Executors wird ForkJoinPool.commonPool()der Thread-Pool so verwendet, wie er intern zum Ausführen des asynchronen Codes verwendet wird. Wenn ein Thread-Pool angegeben ist, wird die Ausführung unter Verwendung des angegebenen Thread-Pools ausgeführt.
Standardmäßig verwendet CompletableFuture den öffentlichen ForkJoinPool-Thread-Pool. Die Anzahl der von diesem Thread-Pool erstellten Threads entspricht standardmäßig der Anzahl der CPU-Kerne (Sie können JVM option:-Djava.util.concurrent.ForkJoinPool.common.parallelismdie Anzahl der Threads im ForkJoinPool-Thread-Pool auch über festlegen). Wenn sich alle CompletableFutures einen Thread-Pool teilen, werden alle Threads im Thread-Pool bei E/A-Vorgängen blockiert, sobald eine Aufgabe einige sehr langsame E/A-Vorgänge ausführt, was zu einem Thread-Ausfall führt und die Leistung des gesamten Systems beeinträchtigt. Daher wird dringend empfohlen, je nach Geschäftstyp unterschiedliche Thread-Pools zu erstellen, um gegenseitige Störungen zu vermeiden.

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
    
    
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
    CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(runnable, threadPool).exceptionally(e -> {
    
    
        System.out.println("当前线程执行失败");
        return null;
    });
    completableFuture.get(30, TimeUnit.SECONDS);
}

Ergebnisse bekommen

  • get()::
  • join()::

Zusammenfassung der Methode:

Fügen Sie hier eine Bildbeschreibung ein

5.2 Szenario

Der berühmte Mathematiker Herr Hua Luogeng stellte in dem Artikel „Gesamtmethode“ ein Beispiel für das Kochen von Wasser zur Teezubereitung vor. In dem Artikel wurde erwähnt, dass der optimale Prozess wie folgt sein sollte:
Fügen Sie hier eine Bildbeschreibung ein
Für den Vorgang des Kochens von Wasser zur Teezubereitung ist eine optimale Aufteilung erforderlich Arbeitslösung: Verwenden Sie zwei Fäden T1 und T2, um den Prozess des Wasserkochens und der Teezubereitung abzuschließen. T1 ist für die drei Prozesse des Waschens des Wasserkochers, des Kochens von Wasser und der Teezubereitung verantwortlich. T2 ist für die drei Prozesse des Waschens der Teekanne verantwortlich , Waschen der Teetassen und Entnehmen der Teeblätter. T1 wird ausgeführt. Beim Zubereiten von Tee müssen Sie warten, bis T2 den Vorgang der Entnahme der Teeblätter abgeschlossen hat.

Umsetzung auf Basis der Zukunft

public class T2 implements Callable<String> {
    
    

    @Override
    public String call() throws Exception {
    
    
        System.out.println("T2:洗茶壶...");
        TimeUnit.SECONDS.sleep(1);

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

        System.out.println("T2:拿茶叶...");
        TimeUnit.SECONDS.sleep(1);
        return "龙井";
    }
    
}
public class T1 implements Callable<String> {
    
    

    private FutureTask<String> t2;

    public T1(FutureTask<String> t2) {
    
    
        this.t2 = t2;
    }

    @Override
    public String call() throws Exception {
    
    
        System.out.println("T1:洗水壶...");
        TimeUnit.SECONDS.sleep(1);

        System.out.println("T1:烧开水...");
        TimeUnit.SECONDS.sleep(15);

        // 获取 T2 的茶叶
        String chaYe = t2.get();
        System.out.println("T1:拿到茶叶:" + chaYe);
        System.out.println("T1:泡茶...");
        return "上茶:" + chaYe;
    }

}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    FutureTask<String> task2 = new FutureTask<>(new T2());
    FutureTask<String> task1 = new FutureTask<>(new T1(task2));

    new Thread(task1).start();
    new Thread(task2).start();
    System.out.println(task1.get());
}

Implementierung basierend auf CompletableFuture

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    //任务1:洗水壶->烧开水
    CompletableFuture<Void> task1 = CompletableFuture.runAsync(() -> {
    
    
        System.out.println("T1:洗水壶...");
        sleep(1, TimeUnit.SECONDS);

        System.out.println("T1:烧开水...");
        sleep(15, TimeUnit.SECONDS);
    });
    //任务2:洗茶壶->洗茶杯->拿茶叶
    CompletableFuture<String> task2 = 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<String> f3 = task1.thenCombine(task2, (x, y) -> {
    
    
        System.out.println("T1:拿到茶叶:" + y);
        System.out.println("T1:泡茶...");
        return "上茶:" + y;
    });
    //等待任务3执行结果
    System.out.println(f3.join());
}

static void sleep(int t, TimeUnit u) {
    
    
    try {
    
    
        u.sleep(t);
    } catch (InterruptedException e) {
    
    
    }
}

Supongo que te gusta

Origin blog.csdn.net/sco5282/article/details/131124596
Recomendado
Clasificación