Heim  >  Artikel  >  Java  >  Wie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?

Wie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?

WBOY
WBOYnach vorne
2023-04-27 08:22:062341Durchsuche

1. Eine Beispielüberprüfung von Future

In einigen Geschäftsszenarien müssen wir Multithreads verwenden, um Aufgaben asynchron auszuführen, um die Aufgabenausführung zu beschleunigen.

JDK5 fügt eine neue Future-Schnittstelle hinzu, die zur Beschreibung der Ergebnisse einer asynchronen Berechnung verwendet wird.

Obwohl Future und verwandte Verwendungsmethoden die Möglichkeit bieten, Aufgaben asynchron auszuführen, ist es sehr unpraktisch, die Ergebnisse zu erhalten. Wir müssen Future.get() verwenden, um den aufrufenden Thread zu blockieren, oder Abfragen verwenden, um Future.isDone zu bestimmen Ob die Aufgabe ist vorbei, und dann erhalten Sie die Ergebnisse.

Beide Verarbeitungsmethoden sind nicht sehr elegant. Der relevante Code lautet wie folgt:

    @Test
    public void testFuture() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<String> future = executorService.submit(() -> {
            Thread.sleep(2000);
            return "hello";
        });
        System.out.println(future.get());
        System.out.println("end");
    }

Gleichzeitig kann Future das Szenario, in dem mehrere asynchrone Aufgaben voneinander abhängen müssen, nicht lösen Der Thread muss warten, bis die Sub-Thread-Aufgabe abgeschlossen ist. Möglicherweise haben Sie zu diesem Zeitpunkt an „CountDownLatch“ gedacht. Der Code lautet wie folgt.

Hier werden zwei Futures definiert. Die erste erhält Benutzerinformationen über die Benutzer-ID und die zweite erhält Produktinformationen über die Produkt-ID.

    @Test
    public void testCountDownLatch() throws InterruptedException, ExecutionException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        CountDownLatch downLatch = new CountDownLatch(2);
        long startTime = System.currentTimeMillis();
        Future<String> userFuture = executorService.submit(() -> {
            //模拟查询商品耗时500毫秒
            Thread.sleep(500);
            downLatch.countDown();
            return "用户A";
        });
 
        Future<String> goodsFuture = executorService.submit(() -> {
            //模拟查询商品耗时500毫秒
            Thread.sleep(400);
            downLatch.countDown();
            return "商品A";
        });
 
        downLatch.await();
        //模拟主程序耗时时间
        Thread.sleep(600);
        System.out.println("获取用户信息:" + userFuture.get());
        System.out.println("获取商品信息:" + goodsFuture.get());
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
 
    }

「Laufende Ergebnisse」

Benutzerinformationen abrufen: Benutzer A
Produktinformationen abrufen: Produkt A
Die Gesamtzeit beträgt 1110 ms

Aus den laufenden Ergebnissen ist ersichtlich, dass die Ergebnisse erhalten wurden, und wenn wir Verwenden Sie keine asynchronen Vorgänge, die Ausführungszeit sollte sein: 500+400+600 = 1500. Nach der Verwendung asynchroner Vorgänge werden tatsächlich nur 1110 verwendet.

Aber nach Java8 halte ich dies nicht mehr für eine elegante Lösung. Als nächstes lernen wir die Verwendung von CompletableFuture kennen.

2. Implementieren Sie das obige Beispiel über CompletableFuture. Sie denken, das ist das Ende, weit mehr als das. CompletableFuture ist viel besser als das.

Zum Beispiel kann es implementiert werden: Nachdem Aufgabe 1 ausgeführt wurde, wird Aufgabe 2 ausgeführt, oder sogar das Ergebnis der Ausführung von Aufgabe 1 kann als Eingabeparameter für Aufgabe 2 und andere leistungsstarke Funktionen verwendet werden. Lernen Sie die API von CompletableFuture .

3. CompletableFuture-Erstellungsmethoden


3.1. 4 häufig verwendete Erstellungsmethoden

Es gibt vier statische Methoden im CompletableFuture-Quellcode, um asynchrone Aufgaben auszuführen hier Ihr Unterschied:

„supplyAsync“

führt Aufgaben aus und unterstützt Rückgabewerte.

「runAsync」

führt die Aufgabe aus und hat keinen Rückgabewert. 3.1.1, „supplyAsync-Methode“

  • 「get() und get(long timeout, TimeUnit unit)」 => ist bereits in Future vorgesehen. Wenn das Ergebnis nicht innerhalb der angegebenen Zeit erhalten wird, wird eine Timeout-Ausnahme ausgelöst

  • 「getNow」 => Erhalten Sie das Ergebnis sofort ohne Blockierung. Wenn die Ergebnisberechnung abgeschlossen ist, wird das Ergebnis zurückgegeben oder es wird eine Ausnahme während des Berechnungsprozesses zurückgegeben valueIfAbsent-Wert wird zurückgegeben

"join"

=> Es wird keine Ausnahme in der Methode ausgelöst

Beispiel:

    @Test
    public void testCompletableInfo() throws InterruptedException, ExecutionException {
        long startTime = System.currentTimeMillis();
 
        //调用用户服务获取用户基本信息
        CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() ->
                //模拟查询商品耗时500毫秒
        {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "用户A";
        });
 
        //调用商品服务获取商品基本信息
        CompletableFuture<String> goodsFuture = CompletableFuture.supplyAsync(() ->
                //模拟查询商品耗时500毫秒
        {
            try {
                Thread.sleep(400);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "商品A";
        });
 
        System.out.println("获取用户信息:" + userFuture.get());
        System.out.println("获取商品信息:" + goodsFuture.get());
 
        //模拟主程序耗时时间
        Thread.sleep(600);
        System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
    }

"Run result":

  • Das erste Ausführungsergebnis ist

    "Produkt B" , da Sie zuerst 1 Sekunde schlafen müssen und das Ergebnis nicht sofort erhalten werden kann

  • Die Join-Methode löst keine Ausnahme aus, aber das Ausführungsergebnis löst eine Ausnahme aus. Die ausgelöste Ausnahme ist CompletionException

    Die get-Methode ruft das Ergebnis ab und die vom Ausführungsergebnis ausgelöste Ausnahme ist ExecutionException
  • So implementieren Sie Java8 CompletableFuture asynchrones Multithreading

    4.1, thenRun/thenRunAsync

    Um es in Laienbegriffen auszudrücken ,
  • "Erledigen Sie nach Abschluss der ersten Aufgabe die zweite Aufgabe und die zweite. Für jede Aufgabe gibt es keinen Rückgabewert"
.

示例

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier){..}
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){..}
public static CompletableFuture<Void> runAsync(Runnable runnable){..}
public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor){..}

「运行结果」:

  • 第一个执行结果为 「商品B」,因为要先睡上1秒结果不能立即获取

  • join方法获取结果方法里不会抛异常,但是执行结果会抛异常,抛出的异常为CompletionException

  • get方法获取结果方法里将抛出异常,执行结果抛出的异常为ExecutionException

4、异步回调方法

Wie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?

4.1、thenRun/thenRunAsync

通俗点讲就是,「做完第一个任务后,再做第二个任务,第二个任务也没有返回值」

示例

//使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
//自定义线程,根据supplier构建执行任务
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

「thenRun 和thenRunAsync有什么区别呢?」

如果你执行第一个任务的时候,传入了一个自定义线程池:

  • 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。

  • 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池。

说明: 后面介绍的thenAccept和thenAcceptAsync,thenApply和thenApplyAsync等,它们之间的区别也是这个。

4.2、thenAccept/thenAcceptAsync

第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,但是回调方法是没有返回值的。

示例

Beispiel🎜🎜
//使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable) 
//自定义线程,根据runnable构建执行任务
public static CompletableFuture<Void> runAsync(Runnable runnable,  Executor executor)
🎜🎜"Was ist der Unterschied zwischen thenRun und thenRunAsync?"🎜🎜🎜Wenn Sie beim Ausführen der ersten Aufgabe einen benutzerdefinierten Thread-Pool übergeben: 🎜🎜🎜🎜Rufen Sie die Methode thenRun auf, um die zweite Aufgabe auszuführen Wenn zwei Aufgaben vorhanden sind, teilen sich die zweite Aufgabe und die erste Aufgabe denselben Thread-Pool. 🎜🎜🎜🎜Wenn Sie thenRunAsync aufrufen, um die zweite Aufgabe auszuführen, verwendet die erste Aufgabe den Thread-Pool, den Sie übergeben haben, und die zweite Aufgabe verwendet den ForkJoin-Thread-Pool. 🎜🎜🎜🎜Erläuterung: Der später eingeführte Unterschied zwischen thenAccept und thenAcceptAsync, thenApply und thenApplyAsync ist auch dieser. 🎜🎜4.2, thenAccept/thenAcceptAsync🎜🎜Nachdem die Ausführung der ersten Aufgabe abgeschlossen ist, wird die zweite Rückrufmethodenaufgabe ausgeführt 🎜Das Ausführungsergebnis der Aufgabe wird als Eingabeparameter an die Rückrufmethode übergeben🎜, aber der Rückruf Methode ist kein Rückgabewert. 🎜🎜Beispiel🎜
    @Test
    public void testCompletableThenAccept() throws ExecutionException, InterruptedException {
        long startTime = System.currentTimeMillis();
        CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {
            return "dev";
 
        });
        CompletableFuture<Void> cp2 = cp1.thenAccept((a) -> {
            System.out.println("上一个任务的返回结果为: " + a);
        });
 
        cp2.get();
    }

4.3、 thenApply/thenApplyAsync

表示第一个任务执行完成后,执行第二个回调方法任务,会将该任务的执行结果,作为入参,传递到回调方法中,并且回调方法是有返回值的。

示例

    @Test
    public void testCompletableThenApply() throws ExecutionException, InterruptedException {
        CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> {
            return "dev";
 
        }).thenApply((a) -> {
            if (Objects.equals(a, "dev")) {
                return "dev";
            }
            return "prod";
        });
 
        System.out.println("当前环境为:" + cp1.get());
 
        //输出: 当前环境为:dev
    }

5、异常回调

当CompletableFuture的任务不论是正常完成还是出现异常它都会调用「whenComplete」这回调函数。

  • 「正常完成」:whenComplete返回结果和上级任务一致,异常为null;

  • 「出现异常」:whenComplete返回结果为null,异常为上级任务的异常;

即调用get()时,正常完成时就获取到结果,出现异常时就会抛出异常,需要你处理该异常。

下面来看看示例

5.1、只用whenComplete

    @Test
    public void testCompletableWhenComplete() throws ExecutionException, InterruptedException {
        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {
 
            if (Math.random() < 0.5) {
                throw new RuntimeException("出错了");
            }
            System.out.println("正常结束");
            return 0.11;
 
        }).whenComplete((aDouble, throwable) -> {
            if (aDouble == null) {
                System.out.println("whenComplete aDouble is null");
            } else {
                System.out.println("whenComplete aDouble is " + aDouble);
            }
            if (throwable == null) {
                System.out.println("whenComplete throwable is null");
            } else {
                System.out.println("whenComplete throwable is " + throwable.getMessage());
            }
        });
        System.out.println("最终返回的结果 = " + future.get());
    }

正常完成,没有异常时:

正常结束
whenComplete aDouble is 0.11
whenComplete throwable is null
最终返回的结果 = 0.11

出现异常时:get()会抛出异常

whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了
 
java.util.concurrent.ExecutionException: java.lang.RuntimeException: 出错了
 at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
 at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1895)

5.2、whenComplete + exceptionally示例

    @Test
    public void testWhenCompleteExceptionally() throws ExecutionException, InterruptedException {
        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {
            if (Math.random() < 0.5) {
                throw new RuntimeException("出错了");
            }
            System.out.println("正常结束");
            return 0.11;
 
        }).whenComplete((aDouble, throwable) -> {
            if (aDouble == null) {
                System.out.println("whenComplete aDouble is null");
            } else {
                System.out.println("whenComplete aDouble is " + aDouble);
            }
            if (throwable == null) {
                System.out.println("whenComplete throwable is null");
            } else {
                System.out.println("whenComplete throwable is " + throwable.getMessage());
            }
        }).exceptionally((throwable) -> {
            System.out.println("exceptionally中异常:" + throwable.getMessage());
            return 0.0;
        });
 
        System.out.println("最终返回的结果 = " + future.get());
    }

当出现异常时,exceptionally中会捕获该异常,给出默认返回值0.0。

whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了
exceptionally中异常:java.lang.RuntimeException: 出错了
最终返回的结果 = 0.0

6、多任务组合回调

Wie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?

6.1、AND组合关系

thenCombine / thenAcceptBoth / runAfterBoth都表示:「当任务一和任务二都完成再执行任务三」

区别在于:

  • 「runAfterBoth」 不会把执行结果当做方法入参,且没有返回值

  • 「thenAcceptBoth」: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值

  • 「thenCombine」:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值

示例

    @Test
    public void testCompletableThenCombine() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);
 
        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务2结束");
            return result;
        }, executorService);
 
        //任务组合
        CompletableFuture<Integer> task3 = task.thenCombineAsync(task2, (f1, f2) -> {
            System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId());
            System.out.println("任务1返回值:" + f1);
            System.out.println("任务2返回值:" + f2);
            return f1 + f2;
        }, executorService);
 
        Integer res = task3.get();
        System.out.println("最终结果:" + res);
    }

「运行结果」

异步任务1,当前线程是:17
异步任务1结束
异步任务2,当前线程是:18
异步任务2结束
执行任务3,当前线程是:19
任务1返回值:2
任务2返回值:2
最终结果:4

6.2、OR组合关系

applyToEither / acceptEither / runAfterEither 都表示:「两个任务,只要有一个任务完成,就执行任务三」

区别在于:

  • 「runAfterEither」:不会把执行结果当做方法入参,且没有返回值

  • 「acceptEither」: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值

  • 「applyToEither」:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值

示例

    @Test
    public void testCompletableEitherAsync() {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());
 
            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);
 
        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 2;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务2结束");
            return result;
        }, executorService);
 
        //任务组合
        task.acceptEitherAsync(task2, (res) -> {
            System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId());
            System.out.println("上一个任务的结果为:" + res);
        }, executorService);
    }

运行结果

//通过结果可以看出,异步任务2都没有执行结束,任务3获取的也是1的执行结果
异步任务1,当前线程是:17
异步任务1结束
异步任务2,当前线程是:18
执行任务3,当前线程是:19
上一个任务的结果为:2

注意

如果把上面的核心线程数改为1也就是

 ExecutorService executorService = Executors.newFixedThreadPool(1);

运行结果就是下面的了,会发现根本没有执行任务3,显然是任务3直接被丢弃了。

异步任务1,当前线程是:17
异步任务1结束
异步任务2,当前线程是:17

6.3、多任务组合

  • 「allOf」:等待所有任务完成

  • 「anyOf」:只要有一个任务完成

示例

allOf:等待所有任务完成

    @Test
    public void testCompletableAallOf() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 1;
            System.out.println("异步任务1结束");
            return result;
        }, executorService);
 
        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 2;
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务2结束");
            return result;
        }, executorService);
 
        //开启异步任务3
        CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> {
            System.out.println("异步任务3,当前线程是:" + Thread.currentThread().getId());
            int result = 1 + 3;
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("异步任务3结束");
            return result;
        }, executorService);
 
        //任务组合
        CompletableFuture<Void> allOf = CompletableFuture.allOf(task, task2, task3);
 
        //等待所有任务完成
        allOf.get();
        //获取任务的返回结果
        System.out.println("task结果为:" + task.get());
        System.out.println("task2结果为:" + task2.get());
        System.out.println("task3结果为:" + task3.get());
    }

anyOf: 只要有一个任务完成

    @Test
    public void testCompletableAnyOf() throws ExecutionException, InterruptedException {
        //创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //开启异步任务1
        CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> {
            int result = 1 + 1;
            return result;
        }, executorService);
 
        //开启异步任务2
        CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> {
            int result = 1 + 2;
            return result;
        }, executorService);
 
        //开启异步任务3
        CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> {
            int result = 1 + 3;
            return result;
        }, executorService);
 
        //任务组合
        CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task, task2, task3);
        //只要有一个有任务完成
        Object o = anyOf.get();
        System.out.println("完成的任务的结果:" + o);
    }

7、CompletableFuture使用有哪些注意点

Wie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?

 CompletableFuture 使我们的异步编程更加便利的、代码更加优雅的同时,我们也要关注下它,使用的一些注意点。

7.1、Future需要获取返回值,才能获取异常信息

    @Test
    public void testWhenCompleteExceptionally() {
        CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> {
            if (1 == 1) {
                throw new RuntimeException("出错了");
            }
            return 0.11;
        });
 
        //如果不加 get()方法这一行,看不到异常信息
        //future.get();
    }

Future需要获取返回值,才能获取到异常信息。如果不加 get()/join()方法,看不到异常信息。

小伙伴们使用的时候,注意一下哈,考虑是否加try...catch...或者使用exceptionally方法。

7.2、CompletableFuture的get()方法是阻塞的

CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,需要添加超时时间。

//反例
 CompletableFuture.get();
//正例
CompletableFuture.get(5, TimeUnit.SECONDS);

7.3、不建议使用默认线程池

CompletableFuture代码中又使用了默认的「ForkJoin线程池」,处理的线程个数是电脑「CPU核数-1」。在大量请求过来的时候,处理逻辑复杂的话,响应会很慢。一般建议使用自定义线程池,优化线程池配置参数。

7.4、自定义线程池时,注意饱和策略

CompletableFuture的get()方法是阻塞的,我们一般建议使用future.get(5, TimeUnit.SECONDS)。并且一般建议使用自定义线程池。

但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离哈。

Das obige ist der detaillierte Inhalt vonWie implementiert Java8 CompletableFuture asynchrone Multithread-Programmierung?. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen
Vorheriger Artikel:Was ist SPI in Java?Nächster Artikel:Was ist SPI in Java?