簡體   English   中英

如何保證ConcurrentHashMap的get()始終返回最新的實際值?

[英]How to guarantee get() of ConcurrentHashMap to always return the latest actual value?

介紹
假設我有一個ConcurrentHashMap單例:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

}

然后,我有來自不同來源的三個后續請求(均由不同的線程處理)。
第一個服務發出一個請求,該請求獲取單例,創建Record實例,生成唯一ID並將其放入Map ,然后將此ID發送給另一個服務。
然后,第二個服務使用該ID發出另一個請求。 它獲取單例,找到Record實例並對其進行修改。
最后(可能是半小時后),第二個服務再次發出請求,以進一步修改Record

問題
在某些非常罕見的情況下,我遇到了heisenbug 在日志中,我可以看到,第一個請求成功將Record放入Map ,第二個請求按ID找到並修改了它,然后第三個請求嘗試按ID查找Record,但未找到任何內容( get()返回null )。
我發現有關ConcurrentHashMap的唯一保證是:

在將對象放入任何並發集合之前,線程中的操作發生在訪問另一個線程中的元素或從集合中刪除該元素之后的操作。

這里 如果我做對了,從字面上看,它意味着get()可以返回某個時候實際上在Map中的任何值,只要它不會破壞happens-before在不同線程之間的動作之間的關系即可。
在我的情況下,它的用法是這樣的:如果第三個請求不關心第一個和第二個處理期間發生了什么,那么它可以從Map讀取null

它不適合我,因為我真的需要從Map獲取最新的實際Record

我嘗試了什么
因此我開始思考,如何在后續Map修改之間建立關系happens-before 並產生了想法。 JLS (在17.4.4)認為:

對易失性變量v的寫操作(第8.3.1.4節)與任何線程對v的所有后續讀取進行同步(其中“后續”是根據同步順序定義的)。

因此,讓我們假設,我將像這樣修改我的單身人士:

public class RecordsMapSingleton {

    private static final ConcurrentHashMap<String,Record> payments = new ConcurrentHashMap<>();
    private static volatile long revision = 0;

    public static ConcurrentHashMap<String, Record> getInstance() {
        return payments;
    }

    public static void incrementRevision() {
        revision++;
    }
    public static long getRevision() {
        return revision;
    }

}

然后,每次修改后MapRecord里面,我會打電話給incrementRevision()並從地圖我任何讀前會打電話給getRevision()


由於heisenbugs的性質,無法進行大量測試就足以證明該解決方案是正確的。 而且我不是並發方面的專家,因此無法正式驗證它。

有人可以認可,采用這種方法可以確保我始終從ConcurrentHashMap獲取最新的實際值嗎? 如果這種方法不正確或效率低下,您可以推薦我其他方法嗎?

您的方法將不起作用,因為您實際上是在重復相同的錯誤。 由於ConcurrentHashMap.putConcurrentHashMap.get將在發生關系之前創建事件,但沒有時間順序保證,因此對volatile變量的讀取和寫入完全相同。 它們關系發生之前發生,但是沒有時間順序保證,如果一個線程碰巧在另一個執行put之前調用get ,則該情況同樣適用於在volatile寫入之前發生的volatile讀取。 除此之外,您還添加了另一個錯誤,因為將++運算符應用於volatile變量不是原子的。

volatile變量所做的保證並不比對ConcurrentHashMap所作的保證強。 它的文檔明確指出:

檢索反映了自發生以來最新完成的更新操作的結果。

JLS聲明外部動作是與程序順序有關的線程間動作:

線程間操作是由一個線程執行的操作,可以被另一個線程檢測或直接影響該操作。 程序可以執行幾種類型的線程間操作:

  • 外部行動 外部動作是在執行外部可以觀察到的動作,其結果基於執行外部的環境。

簡而言之,如果一個線程放入ConcurrentHashMap並向外部實體發送消息,而第二個線程在從外部實體接收到一條消息(取決於先前發送的消息)后從同一個ConcurrentHashMap獲取消息,則無法看到內存問題。

可能是因為這些操作未采用這種方式進行編程,或者外部實體沒有假定的依賴關系,但也可能是錯誤位於完全不同的區域,但我們無法如您所願沒有發布相關代碼,例如密鑰不匹配或打印代碼錯誤。 但是不管它是什么,它都不會由volatile變量解決。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM