JUC-Parallelprogrammierung (2) ForkJoinPool, Future, CompletableFuture, CAS

ForkJoin

Partition

Das größte Merkmal des ForkJoinPool-Thread-Pools ist Fork and Join (Join), das eine große Aufgabe in mehrere kleine Aufgaben aufteilt und diese parallel ausführt , kombiniert mit dem **Arbeitsdiebstahlmodus (Worksteal)**, um die Gesamtausführungseffizienz zu verbessern. Nutzen Sie die CPU-Ressourcen voll aus.

Fügen Sie hier eine Bildbeschreibung ein

Jobdiebstahl

Unter Arbeitsdiebstahl versteht man das Stehlen von Aufgaben aus den Aufgabenwarteschlangen anderer Threads zur Ausführung, wenn sich in der Aufgabenwarteschlange eines bestimmten Threads keine ausführbare Aufgabe befindet, um die Rechenleistung des Arbeitsthreads voll auszunutzen und die Anzahl der auszuführenden Aufgaben zu verringern Threads aufgrund unzureichender Erfassung. Die durch die Aufgabe verursachte Leerlaufverschwendung.

In ForkJoinpool verwenden alle Warteschlangen für Arbeitsaufgaben Deque-Container mit doppelter Warteschlange. Wir wissen, dass wir bei der gemeinsamen Verwendung von Warteschlangen alle am Ende der Warteschlange einfügen und am Kopf der Warteschlange konsumieren, um FIFO zu erreichen. Und um Arbeitsplätze zu stehlen. Im Allgemeinen ändern wir den Arbeitsthread in der Arbeitswarteschlange in LIFO, und wenn wir Aufgaben von anderen Threads stehlen, holen wir sie vom Kopf der Warteschlange. Das schematische Diagramm sieht wie folgt aus:

Fügen Sie hier eine Bildbeschreibung ein
Die Worker-Threads Worker1, Worker2 und Worker3 erhalten alle Aufgaben, indem sie am Ende der TaskQueue auftauchen, und Aufgaben werden auch vom Ende gepusht. Wenn sich keine Aufgabe in der Warteschlange von Worker3 befindet, muss sie aus den Warteschlangen anderer Threads gestohlen werden , damit Worker3 nicht ohne Aufgaben im Leerlauf ist. Dies ist das Grundprinzip des Work-Stealing-Algorithmus.
Es ist denkbar, dass, wenn der Work-Stealing-Algorithmus nicht verwendet wird, einige Arbeiter während des kontinuierlichen Fork-Prozesses auf den Beitritt warten. Die Idee des Arbeitsdiebstahls ist bei der zugrunde liegenden Verarbeitung von Golang-Coroutinen tatsächlich dieselbe.

ForkJoinPool und ThreadPoolExecutor

Sowohl ForkJoinPool als auch ThreadPoolExecutor implementieren die Schnittstellen Executor und ExecutorService, und beide können die Anzahl der Threads über den Konstruktor threadFactory festlegen. Sie können den Quellcode der Methode ForkJoinPool.makeCommonPool() anzeigen, um die Konstruktionsdetails des gemeinsamen Thread-Pools anzuzeigen.

Was die interne Struktur betrifft, liegt meiner Meinung nach der größte Unterschied zwischen den beiden Thread-Pools im Design der Arbeitswarteschlange, wie
ThreadPoolExecutor:
Fügen Sie hier eine Bildbeschreibung ein
ForkJoinPool:
Fügen Sie hier eine Bildbeschreibung ein
in der folgenden Abbildung dargestellt:
ForkJoinPool每个线程都有自己的队列
ThreadPoolExecutor共用一个队列

ForkJoinPool eignet sich am besten für rechenintensive Aufgaben und vorzugsweise für nicht blockierende Aufgaben.

Anwendungsfälle

In JUC gibt es zwei Klassen zum Implementieren des Fork-Join-Frameworks, nämlich ForkJoinPool und die abstrakte Klasse ForkJoinTask für übermittelte Aufgaben. Obwohl es für ForkJoinTask viele Unterklassen gibt, verwenden wir im Grundgebrauch RecursiveTask mit Rückgabewert und die RecursiveAction-Klasse ohne Rückgabewert.
Fügen Sie hier eine Bildbeschreibung ein

Berechnung ohne Rückgabewert – RecursiveAction

Fall: Realisieren Sie das Drucken von 50 Tasksequenzen

  • Der erste Schritt besteht darin, die zu verarbeitende printForkAction zu erstellen, die von RecursiveAction geerbt wird:
  • Schritt 2: Schreiben Sie die Methode „compute()“ neu. Die Forkjoin-Divide-and-Conquer-Idee spiegelt sich hier wider. start ist die Seriennummer der Startaufgabe, en ist die Seriennummer der Endaufgabe und legt den Schwellenwert für die Anzahl fest Aufgaben.
    • Wenn die Anzahl der zu verarbeitenden Tasksequenzen unter dem Schwellenwert liegt, führen Sie eine Schleife durch und verarbeiten Sie sie direkt.
    • Wenn die Anzahl der zu verarbeitenden Aufgabensequenzen größer oder gleich dem Schwellenwert ist, teilen Sie die zu verarbeitenden Aufgaben auf (normalerweise in der Mitte), erstellen Sie zwei neue printForkActions und erstellen Sie danninvokeAll(firstTask, secondTask);
class  printForkAction extends RecursiveAction {
    
    
    private static final int threshold = 5;
    private int start;
    private int end;

    public printForkAction(int start, int end) {
    
    
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
    
    
        if (end - start < threshold) {
    
    
            for (int i = start; i < end; ++i) {
    
    
                // 业务
                System.out.println(Thread.currentThread().getName() + ":i=" + i);
            }
        } else {
    
    
            int mid = start + ((end - start) / 2);
            printForkAction firstTask = new printForkAction(start, mid);
            printForkAction secondTask = new printForkAction(mid + 1, end);
            invokeAll(firstTask, secondTask);
        }
    }
}

Schritt 3: Erstellen Sie ForkJoinPool und senden Sie printForkAction daran.

    public static void main(String[] args) {
    
    
        ForkJoinPool pool = new ForkJoinPool();
        pool.submit(new printForkAction(1, 50));
        try {
    
    
            pool.awaitTermination(2, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        pool.shutdown();
    }

Ergebnis: Mehrere Threads werden implementiert, um gemeinsam Aufgaben zu erledigen, die größer sind als die Aufgabensequenznummer.
Fügen Sie hier eine Bildbeschreibung ein

Berechnungen mit Rückgabewerten – RecursiveTask

Fall: Berechnen Sie die Summe von 1-100 Millionen.
Die Schritte ähneln den oben genannten, der Unterschied besteht darin, dass RecursiveTask einen Rückgabewert hat:

  • 1. Beim Erben von RecursiveTask ist der generische Typ der angegebene Rückgabewerttypextends RecursiveTask<Long>
  • 2. return firstTask.join() + secondTask.join();Das Aufgabenergebnis kann hier zurückgegeben werden.
  • 3. ForkJoinTask<Long> task = pool.submit(new computeForkTask(1L, 100_000_00L));Senden Sie die Aufgabe und geben Sie ein ForkJoinTask-Objekt zurück. Der generische Typ ist weiterhin der Rückgabewerttyp
  • 4. Long ans = task.get();Rufen Sie get() auf, um das Ergebnis zu erhalten.
    public static void main(String[] args) {
    
    
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = pool.submit(new computeForkTask(1L, 100_000_00L));
        try {
    
    
            System.out.println("-------");
            Long ans = task.get();
            System.out.println("-------");
            System.out.println(ans);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

class computeForkTask extends RecursiveTask<Long> {
    
    
    private Long start;
    private Long end;
    static final Long threshold = 100L;

    public computeForkTask(Long start, Long end) {
    
    
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
    
    
        Long ans = 0L;
        if (end - start < threshold) {
    
    
            for (Long i = start; i < end; ++i) {
    
    
                // 业务
                ans += i;
            }
            return  ans;
        } else {
    
    
            Long mid = start + ((end - start) / 2);
            computeForkTask firstTask = new computeForkTask(start, mid);
            computeForkTask secondTask = new computeForkTask(mid + 1, end);
            invokeAll(firstTask, secondTask);
            return firstTask.join() + secondTask.join();
        }
    }
}

Zukünftiger asynchroner Rückruf

Future stellt das Ergebnis einer asynchronen Aufgabe dar, die möglicherweise noch nicht abgeschlossen wurde. Zu diesem Ergebnis kann ein Rückruf hinzugefügt werden, um entsprechende Maßnahmen zu ergreifen, nachdem die Aufgabe erfolgreich war oder fehlgeschlagen ist.

Die Future-Schnittstelle umfasst hauptsächlich 5 Methoden:
Fügen Sie hier eine Bildbeschreibung ein

  • Die Methode get() kann ein Ergebnis zurückgeben, wenn die Aufgabe abgeschlossen ist. Wenn die Arbeit beim Aufruf noch nicht abgeschlossen ist, wird der Thread blockiert, bis die Aufgabe abgeschlossen ist.
  • get (langes Timeout, TimeUnit-Einheit) gibt das Ergebnis zurück, nachdem auf die Timeout-Zeit gewartet wurde
  • Mit der Methode cancel (boolean mayInterruptIfRunning) kann eine Aufgabe gestoppt werden. Wenn die Aufgabe gestoppt werden kann (beurteilt durch mayInterruptIfRunning), kann sie „true“ zurückgeben. Wenn die Aufgabe abgeschlossen oder gestoppt wurde oder nicht gestoppt werden kann, wird dies der Fall sein falsch zurückgeben.
  • isDone()-Methode, um festzustellen, ob die aktuelle Methode abgeschlossen ist
  • isCancel()-Methode, um zu bestimmen, ob die aktuelle Methode abgebrochen wird

Fall mit kochendem Wasser

Fügen Sie hier eine Bildbeschreibung ein
Zwei Threads T1 und T2 werden verwendet, um den Prozess des Kochens von Wasser und der Zubereitung von Tee abzuschließen. T1 ist für die drei Prozesse des Waschens des Wasserkochers, des Kochens von Wasser und der Zubereitung von Tee verantwortlich. T2 ist für die drei Prozesse des Waschens der Teekanne und des Waschens verantwortlich die Teetasse und nimmt die Teeblätter. T1 führt die drei Prozesse der Teezubereitung durch. Dieser Prozess muss warten, bis T2 den Prozess der Teeblattentnahme abgeschlossen hat.

Nehmen Sie an der Umsetzung teil

  • Thread A ruft die Join-Methode von Thread B auf. Wenn Thread B nicht ausgeführt wird, ist Thread A immer blockiert.
  • Join ist eine Instanzmethode, die mit einem Thread-Objekt aufgerufen werden muss
  • Beim Zusammenführen von Threads mithilfe des Join-Threads kann der Rückgabewert des zusammengeführten Threads nicht ermittelt werden, dh das Ausführungsergebnis des Threads mit kochendem Wasser kann nicht bekannt sein. Sie können nur blockieren und auf das Ende des kochenden Wasserfadens warten

Dann können wir zwei Threads erstellen, einen zum Kochen von Wasser, einen zum Geschirrspülen, diese mit dem Haupt-Thread verbinden und dann Tee zubereiten:

public class JoinDemo {
    
    
    public static final int SLEEP_TIME = 1000;

    public static void main(String[] args) {
    
    
      Thread hThread = new Thread(() -> {
    
    
            try {
    
    
                Thread.currentThread().setName("烧水线程");
                System.out.println("洗好水壶");
                System.out.println("灌好凉水");
                System.out.println("放在火上");
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("水烧开了");
        });
      hThread.start();

        Thread wThread = new Thread(() -> {
    
    
            try {
    
    
                Thread.currentThread().setName("清洗线程");
                System.out.println("洗茶壶");
                System.out.println("洗茶杯");
                System.out.println("拿茶叶");
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("洗茶叶完成");
        });
        wThread.start();

        // 主线程 1. 合并烧水线程
        try {
    
    
            hThread.join();
            wThread.join();
            System.out.println("泡泡茶喝");
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
    }
}

FutureTask-Implementierung

Kochendes Wasser und Geschirr spülen werden jeweils als aufrufbare Objekte konstruiert, mit FutureTask zusammengesetzt und erhalten das Ergebnis von FutureTask im Hauptthread:

public class FutureTaskDemo {
    
    

    public static final int SLEEP_TIME = 1000;

    public static void main(String[] args) {
    
    
        Callable<Boolean> hotWaterJob = new HotWaterJob();
        FutureTask<Boolean> hotWaterTask = new FutureTask<>(hotWaterJob);
        Thread hotWaterThread = new Thread(hotWaterTask, "烧水线程");

        Callable<Boolean> washJob = new WashJob();
        FutureTask<Boolean> washTask = new FutureTask<>(washJob);
        Thread washThread = new Thread(washTask, "清洗线程");
        hotWaterThread.start();
        washThread.start();

        try {
    
    
            Boolean hotWaterFlag = hotWaterTask.get();
            Boolean washFlag = washTask.get();
            drinkTea(hotWaterFlag, washFlag);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

    private static void drinkTea(Boolean hotWaterFlag, Boolean washFlag) {
    
    
        if (hotWaterFlag && washFlag) {
    
    
            System.out.println("喝茶");
        }
    }


    static class HotWaterJob implements Callable<Boolean> {
    
    

        @Override
        public Boolean call() throws Exception {
    
    
            try {
    
    
                Thread.currentThread().setName("烧水线程");
                System.out.println("洗好水壶");
                System.out.println("灌好凉水");
                System.out.println("放在火上");
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
                return false;
            }
            System.out.println("水烧开了");
            return true;
        }
    }

    static class WashJob implements Callable<Boolean> {
    
    
        @Override
        public Boolean call() throws Exception {
    
    
            try {
    
    
                Thread.currentThread().setName("清洗线程");
                System.out.println("洗茶壶");
                System.out.println("洗茶杯");
                System.out.println("拿茶叶");
                Thread.sleep(SLEEP_TIME);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
                return false;
            }
            System.out.println("洗茶叶完成");
            return true;
        }
    }
}

Abschließbare Zukunft

CompletableFuture und FutureTask sind Beispiele, beide sind Implementierungsklassen von Future. Im Vergleich zur traditionellen Zukunft unterstützt es viele neue Funktionen wie Streaming Computing, funktionale Programmierung, Abschlussbenachrichtigung und benutzerdefinierte Ausnahmebehandlung. Da in Java immer mehr funktionale Programmierung zum Einsatz kommt, ist die Beherrschung von CompletableFuture sehr wichtig, um die wichtigsten neuen Funktionen nach Java 8 besser nutzen zu können.

Warum heißt es CompletableFuture?

CompletableFuture bedeutet wörtlich übersetzt „Completable Future“. Im Vergleich zur herkömmlichen Zukunft kann CompletableFuture den Ergebniswert der Berechnung aktiv festlegen (den Berechnungsprozess aktiv beenden, d. h. abschließbar) , um in einigen Szenarien die Blockierungswartezeit aktiv zu beenden. Da Future den Wert des Berechnungsergebnisses nicht aktiv festlegen kann, wird get() nach dem Aufruf zum Blockieren und Warten entweder zurückgegeben, wenn das Berechnungsergebnis generiert wird, oder es kommt zu einer Zeitüberschreitung.

Das folgende Beispiel veranschaulicht kurz, wie CompletableFuture aktiv abgeschlossen wird. Da im folgenden Codeteil die vollständige Methode aufgerufen wird, lautet das endgültige Druckergebnis „manueller Test“ statt „Test“.

CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
    
    
    try{
    
    
        Thread.sleep(1000L);
        return "test";
    } catch (Exception e){
    
    
        return "failed test";
    }
});
future.complete("manual test");
System.out.println(future.join());

Erstellen Sie eine asynchrone Aufgabe

SupplyAsync

SupplyAsync dient zum Erstellen einer asynchronen Aufgabe mit einem Rückgabewert. Es verfügt über die folgenden zwei Methoden: Eine besteht darin, den Standard-Thread-Pool (ForkJoinPool.commonPool()) zu verwenden, und die andere ist eine überladene Methode mit einem benutzerdefinierten Thread-Pool:

// 带返回值异步请求,默认线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
 
// 带返回值的异步请求,可以自定义线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

Spezifische Verwendung:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("do something....");
            return "result";
        });
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 自定义线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println("do something....");
            return "result";
        }, executorService);
 
        //等待子任务执行完成
        System.out.println("结果->" + cf.get());
}

runAsync

runAsync dient zum Erstellen einer asynchronen Aufgabe, die keinen Wert zurückgibt. Es verfügt über die folgenden zwei Methoden: Eine ist die Methode, die den Standard-Thread-Pool verwendet (ForkJoinPool.commonPool()), und die andere ist die überladene Methode mit einem benutzerdefinierten Thread-Pool

// 不带返回值的异步请求,默认线程池
public static CompletableFuture<Void> runAsync(Runnable runnable)
 
// 不带返回值的异步请求,可以自定义线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

Spezifische Verwendung:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
    
    
            System.out.println("do something....");
        });
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        // 自定义线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> {
    
    
            System.out.println("do something....");
        }, executorService);
 
        //等待任务执行完成
        System.out.println("结果->" + cf.get());
}

So erhalten Sie Aufgaben

// 如果完成则返回结果,否则就抛出具体的异常
public T get() throws InterruptedException, ExecutionException 
 
// 最大时间等待返回结果,否则就抛出具体异常
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
 
// 完成时返回结果值,否则抛出unchecked异常。为了更好地符合通用函数形式的使用,如果完成此 CompletableFuture所涉及的计算引发异常,则此方法将引发unchecked异常并将底层异常作为其原因
public T join()
 
// 如果完成则返回结果值(或抛出任何遇到的异常),否则返回给定的 valueIfAbsent。
public T getNow(T valueIfAbsent)
 
// 如果任务没有完成,返回的值设置为给定值
public boolean complete(T value)
 
// 如果任务没有完成,就抛出给定异常
public boolean completeExceptionally(Throwable ex) 
 

注意::

  1. Der Unterschied zwischen join() und get():
    • Die Methode join() löst eine uncheckException (d. h. RuntimeException) aus, die Entwickler nicht zum Auslösen zwingt
    • Die get()-Methode löst eine geprüfte Ausnahme aus, ExecutionException, InterruptedException muss vom Benutzer manuell behandelt werden (werfen oder versuchen, abzufangen).
  2. Der Unterschied zwischen complete() und getNow():
    • Complete(): Wenn die Aufgabe nicht abgeschlossen ist, setzen Sie den zurückgegebenen Wert auf den angegebenen Wert und beenden Sie ihn vorzeitig. Complete() ist nur eine Einstellung für das Ende der Ergebnisübermittlung und gibt nicht das Aufgabenergebnis zurück.
    • getNow(): Gibt den Ergebniswert zurück, wenn der Vorgang abgeschlossen ist (oder löst eine aufgetretene Ausnahme aus), andernfalls wird der angegebene WertIfAbsent zurückgegeben.

Asynchrone Rückrufverarbeitung

1. dann Apply und dann ApplyAsync

thenApply bezeichnet die Aktion, die nach der Ausführung einer bestimmten Aufgabe ausgeführt wird, nämlich die Rückrufmethode, die das Ausführungsergebnis der Aufgabe, also den Rückgabewert der Methode, als Eingabeparameter an die Rückrufmethode mit dem Rückgabewert übergibt .

Der Unterschied zwischen thenApply und thenApplyAsync besteht darin, dass bei Verwendung der thenApply-Methode die Unteraufgabe und die übergeordnete Aufgabe denselben Thread verwenden, während thenApplyAsync einen anderen Thread startet, um die Aufgabe in der Unteraufgabe auszuführen, und thenApplyAsync den Thread-Pool anpassen kann. ForkJoinPool.commonPool ist Wird standardmäßig verwendet. ()Thread-Pool.

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    System.out.println("thenApplyAsync");
    CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println(Thread.currentThread() + " cf1 do something....");
        return 1;
    });

    CompletableFuture<Integer> cf2 = cf1.thenApplyAsync((result) -> {
    
    
        System.out.println(Thread.currentThread() + " cf2 do something....");
        result += 2;
        return result;
    });
    System.out.println(Thread.currentThread() + "---main()");
    //等待任务1执行完成
    System.out.println("cf1结果->" + cf1.get());
    //等待任务2执行完成
    System.out.println("cf2结果->" + cf2.get());
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
    System.out.println("thenApply");
    CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
        System.out.println(Thread.currentThread() + " cf1 do something....");
        return 1;
    });

    CompletableFuture<Integer> cf2 = cf1.thenApply((result) -> {
    
    
        System.out.println(Thread.currentThread() + " cf2 do something....");
        result += 2;
        return result;
    });
    System.out.println(Thread.currentThread() + "---main()");
    //等待任务1执行完成
    System.out.println("cf1结果->" + cf1.get());
    //等待任务2执行完成
    System.out.println("cf2结果->" + cf2.get());
}

Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein

2. thenAccept und thenAcceptAsync

thenAccep bedeutet die Aktion, die nach der Ausführung einer bestimmten Aufgabe ausgeführt wird, also die Rückrufmethode. Das Ausführungsergebnis der Aufgabe, also der Rückgabewert der Methode, wird als Eingabeparameter an die Rückrufmethode übergeben und dort weitergegeben ist kein Rückgabewert.

thenAccept和thenAcceptAsync与thenApply和thenApplyAsync的区别在于accept无返回值,只接受有返回值的future的结果,自己本身无返回值

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Void> cf2 = cf1.thenAccept((result) -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
        });
 
        //等待任务1执行完成
        System.out.println("cf1结果->" + cf1.get());
        //等待任务2执行完成
        System.out.println("cf2结果->" + cf2.get());
}
 
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Void> cf2 = cf1.thenAcceptAsync((result) -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
        });
 
        //等待任务1执行完成
        System.out.println("cf1结果->" + cf1.get());
        //等待任务2执行完成
        System.out.println("cf2结果->" + cf2.get());
}

Fügen Sie hier eine Bildbeschreibung ein

3.thenRun und thenRunAsync

thenRun gibt die Aktion an, die nach der Ausführung einer bestimmten Aufgabe ausgeführt werden soll, nämlich die Rückrufmethode, ohne Eingabeparameter und ohne Rückgabewert.

4. whenComplete und whenCompleteAsync

whenComplete ist eine Rückrufmethode, die nach der Ausführung einer Aufgabe ausgeführt wird. Sie übergibt das Ausführungsergebnis oder die während der Ausführung ausgelöste Ausnahme an die Rückrufmethode. Wenn sie normal ausgeführt wird, ist die Ausnahme null. Das Ergebnis von CompletableFuture entspricht Die Rückrufmethode stimmt mit der Aufgabe überein. Wenn die Aufgabe normal ausgeführt wird, gibt die Get-Methode das Ausführungsergebnis zurück, und wenn die Ausführung abnormal ist, löst die Get-Methode eine Ausnahme aus.

whenComplete 和 thenApply主要区别在于

  • whenComplete方法会传递异常,而thenApply不会传递异常。
  • whenComplete也是没有返回值的
    Fügen Sie hier eine Bildbeschreibung ein

5, handle und handleAsync

Es ist im Grunde dasselbe wie whenComplete, der Unterschied besteht darin, dass die Rückrufmethode von handle einen Rückgabewert hat.

public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            // int a = 1/0;
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = cf1.handle((result, e) -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
            System.out.println("上个任务结果:" + result);
            System.out.println("上个任务抛出异常:" + e);
            return result+2;
        });
 
        //等待任务2执行完成
        System.out.println("cf2结果->" + cf2.get());
}

Kombinierte Multitasking-Verarbeitung: thenCombine, thenAcceptBoth und runAfterBoth

1. Die drei Methoden thenCombine, thenAcceptBoth und runAfterBoth
kombinieren alle zwei CompletableFutures zur Verarbeitung. Erst wenn beide Aufgaben normal abgeschlossen sind, wird mit der nächsten Phase der Aufgabe fortgefahren.

der Unterschied:

  • thenCombine verwendet die Ausführungsergebnisse der beiden Aufgaben als Parameter der bereitgestellten Funktion, und die Methode hat einen Rückgabewert.
  • thenAcceptBoth verwendet auch die Ausführungsergebnisse der beiden Aufgaben als Methodenparameter, hat jedoch keinen Rückgabewert.
  • runAfterBoth hat keine Parameter und keinen Rückgabewert. Beachten Sie, dass die abnormalen Informationen als Ausführungsergebnis der angegebenen Aufgabe verwendet werden, solange eine der beiden Aufgaben abnormal ausgeführt wird.
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
            return 2;
        });
 
        CompletableFuture<Integer> cf3 = cf1.thenCombine(cf2, (a, b) -> {
    
    
            System.out.println(Thread.currentThread() + " cf3 do something....");
            return a + b;
        });
 
        System.out.println("cf3结果->" + cf3.get());
}
 
 public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
            return 2;
        });
        
        CompletableFuture<Void> cf3 = cf1.thenAcceptBoth(cf2, (a, b) -> {
    
    
            System.out.println(Thread.currentThread() + " cf3 do something....");
            System.out.println(a + b);
        });
 
        System.out.println("cf3结果->" + cf3.get());
}
 
public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf1 do something....");
            return 1;
        });
 
        CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
    
    
            System.out.println(Thread.currentThread() + " cf2 do something....");
            return 2;
        });
 
        CompletableFuture<Void> cf3 = cf1.runAfterBoth(cf2, () -> {
    
    
            System.out.println(Thread.currentThread() + " cf3 do something....");
        });
 
        System.out.println("cf3结果->" + cf3.get());
}

CAS

CAS (Compare and Swap) heißt Vergleich und Austausch und bezieht sich normalerweise auf eine atomare Operation: Vergleichen Sie für eine Variable zunächst, ob ihr Speicherwert mit einem erwarteten Wert übereinstimmt, und weisen Sie ihr bei Übereinstimmung einen neuen Wert zu Es. Nehmen wir den ursprünglichen Speicherwert als A, den erwarteten Wert als B und den neuen Wert als C. Die CAS-Operation besteht darin, A mit B zu vergleichen. Wenn A==B, ersetzen Sie den Wert von A durch C; wenn A und B sind unterschiedlich. Wenn sie gleich sind, bedeutet dies, dass andere Unternehmen Daten A geändert haben, sodass der Wert von A nicht auf C aktualisiert wird.

Aus der obigen Erklärung können wir ersehen, dass CAS mit der Idee des optimistischen Sperrens implementiert wird, jedoch keine Sperren verwendet, was viel effizienter ist als das synchronisierte pessimistische Sperren. Die Inkrementierungsoperation in der Java-Atomklasse wird durch CAS erreicht drehen.

Atomklasse

Das Atomic-Paket unter JUC bietet eine Reihe von Klassen, die einfach zu bedienen und leistungsstark sind und Thread-Sicherheit zum Aktualisieren grundlegender Typvariablen, Array-Elemente, Referenztypen und Aktualisierungsfeldtypen in Objekten gewährleisten können. Diese Klassen im Atomic-Paket verwenden die optimistische Sperrstrategie, um Daten atomar zu aktualisieren, und in Java werden sie mithilfe von CAS-Operationen implementiert.

CompareAndSet (V, A)
erwartet V und der eingestellte Wert ist A. Das heißt, nur wenn der Wert der atomaren Variablen a im aktuellen Speicher V ist, wird die Variable auf A aktualisiert.

AtomicInteger a = new AtomicInteger(100);
System.out.println(a.get());
System.out.println(a.compareAndSet(10, 11));
System.out.println(a.get());
System.out.println(a.compareAndSet(100, 11));
System.out.println(a.get());
System.out.println(a.compareAndSet(100, 12));
System.out.println(a.get());
System.out.println(a.compareAndSet(11, 12));
System.out.println(a.get());

Ergebnis:
Fügen Sie hier eine Bildbeschreibung ein
Die unterste Ebene ist die Methode „compareAndSet()“ der Klasse „Unsafe“ mit dem Namen:
Fügen Sie hier eine Bildbeschreibung ein

Unsichere Klasse

Java kann nicht direkt auf das zugrunde liegende Betriebssystem zugreifen, sondern über lokale native Methoden, hinterlässt aber dennoch eine Hintertür – die Unsafe-Klasse, die einige Operationen auf niedriger Ebene bereitstellt, wie z. B. direkten Speicherzugriff usw. Die Unsafe-Klasse stellt auch native Methoden bereit CAS-Operationen:

/** 拿对象o在内存偏移offset处的对象与expected比较,如果相等,则设置o.offset=x并返回true,否则返回false */
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
/** 拿对象o在内存偏移offset处的long值与expected比较,如果相等则设置o.offset=x */
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
/** 拿对象o在内存偏移offset处的int值与expected比较,如果相等则设置o.offset=x */
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);

/** 获取字段f的偏移量 */
public native long objectFieldOffset(Field f);
/** 获取静态field在对象中的偏移量 */
public native long staticFieldOffset(Field f);                                           

Atomare Referenzen lösen das ABA-Problem

Das Problem der CAS-Sperre besteht darin, dass unsere CAS-Sperre ungültig wird, wenn ein Thread den erwarteten Wert A in B und dann B wieder in A ändert.

Um dieses Problem zu lösen, verwenden Sie AtomicStampedReference, eine atomare Referenzklasse, um der Variablen eine Versionsnummer hinzuzufügen. Wenn Sie die Variable erhalten, wird bei jeder Änderung die Versionsnummer + 1 hinzugefügt. Nur wenn die Versionsnummer mit der ursprünglichen übereinstimmt Können Sie diese erfolgreich ändern? Dies vermeidet das ABA-Problem.

 public static void main(String[] args) {
    
    
     AtomicStampedReference<Integer> as = new AtomicStampedReference<>(1,1000);
     new Thread(() ->{
    
    
         System.out.println("a1--" + as.getReference());
         System.out.println(as.compareAndSet(1, 2, as.getStamp(), as.getStamp() + 1));
         System.out.println("a2--" + as.getReference());
         System.out.println(as.compareAndSet(2, 1, as.getStamp(), as.getStamp() + 1));
         System.out.println("a3--" + as.getReference());
     }).start();

     new Thread(() -> {
    
    
         int stamp = as.getStamp();
         try {
    
    
             TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
    
    
             e.printStackTrace();
         }
         System.out.println("b1:" + as.compareAndSet(1, 2, stamp, stamp + 1));
         System.out.println("b1--" + as.getReference());
     }).start();
 }

Supongo que te gusta

Origin blog.csdn.net/baiduwaimai/article/details/132107569
Recomendado
Clasificación