Java CompletableFuture因在上下文中使用共享變數,導致執行緒安全問題

翎野君發表於2024-07-10

背景

在使用CompletableFuture.supplyAsync()時,多個非同步中,同時共用的一個查詢物件引數,而且在這多個任務中間會穿插地對這個物件進行更改,出現的現象就是可能會導致最終get()結果不符合我們的預期。最終調整方案就是在每個任務supplyAsync()之前單獨賦予一個新的final物件只為此任務使用,不再進行共用。

CompletableFuture簡介

CompletableFuture是Java8引入的一個類,用於在非同步程式設計中處理多個任務。它可以將任務連結起來,使得一個任務的結果可以作為另一個任務的輸入。CompletableFuture提供了豐富的方法來處理任務的結果,例如處理異常、合併多個任務的結果等。

CompletableFuture可以透過工廠方法建立,例如CompletableFuture.supplyAsync()用於執行一個有返回值的非同步任務,CompletableFuture.runAsync()用於執行一個沒有返回值的非同步任務。

CompletableFuture的執行緒安全問題
雖然CompletableFuture提供了強大的功能,但在多執行緒環境中使用時,需要注意其執行緒安全問題。

1. 共享變數引發的問題

如果多個任務共享一個變數,並且對該變數進行修改操作,可能會導致不確定的結果。例如下面的程式碼:

public class CompletableFutureDemo {
    private static int count = 0;

    public static void main(String[] args) {
        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
        });

        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            for (int i = 0; i < 1000; i++) {
                count++;
            }
        });

        CompletableFuture<Void> combinedFuture = CompletableFuture.allOf(future1, future2);
        combinedFuture.join();

        System.out.println("Count: " + count);
    }
}

在上面的程式碼中,兩個任務分別對count變數進行1000次增加操作。由於count變數是共享的,這個操作並不是執行緒安全的。當兩個任務交替執行時,可能會導致count的值不是預期的2000。

2. 競態條件

CompletableFuture的方法可以透過多個執行緒同時呼叫,這可能導致競態條件。例如,以下程式碼中的thenApply()方法會在前一個任務完成後執行,返回一個新的CompletableFuture物件。

public class CompletableFutureDemo {
    private static int count = 0;

    public static void main(String[] args) {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
            return 1;
        });

        CompletableFuture<Integer> future2 = future1.thenApply((result) -> {
            return result + 2;
        });

        CompletableFuture<Integer> future3 = future1.thenApply((result) -> {
            return result * 2;
        });

        CompletableFuture<Integer> combinedFuture = CompletableFuture.allOf(future2, future3);
        combinedFuture.join();

        System.out.println("Future2 result: " + future2.get());
        System.out.println("Future3 result: " + future3.get());
    }
}

在上面的程式碼中,future2和future3都依賴於future1的結果,但它們之間並沒有明確的順序關係。如果多個執行緒同時呼叫thenApply方法,可能會導致future2和future3的結果不一致,因為它們有可能使用了不同的future1結果。

解決CompletableFuture的執行緒安全問題

為了解決CompletableFuture的執行緒安全問題,可以採取以下措施:

避免共享變數:在多個任務之間儘量避免共享變數,使用區域性變數或者將變數作為方法引數傳遞。
使用執行緒安全的資料結構:如果必須共享變數,可以使用執行緒安全的資料結構,例如AtomicInteger代替int。
使用同步機制:可以使用synchronized關鍵字或者Lock介面來保證多個任務的互斥訪問。

本篇文章如有幫助到您,請給「翎野君」點個贊,感謝您的支援。

首發連結:https://www.cnblogs.com/lingyejun/p/18293069

相關文章