簡體   English   中英

Java ConcurrentHashMap

[英]Java ConcurrentHashMap

在一個由1個線程負責連續更新映射並且主線程定期讀取該映射的應用程序中,使用ConcurrentHashmap是否足夠? 還是應該在同步塊中顯式鎖定操作? 任何解釋都很好。

更新

我有一個用於地圖的getter和setter(封裝在一個自定義類型中),兩個線程可以同時使用它,ConcurrentHashMap還是一個好的解決方案嗎? 還是我應該同步getter / setter(或者聲明實例變量為volatile)? 只想確保這個額外的細節不會改變解決方案。

只要在對並發哈希映射的一個方法調用中執行所有操作,就無需使用其他鎖定。 不幸的是,如果您需要原子地執行多種方法,則必須使用鎖定,在這種情況下,使用並發哈希映射無濟於事,您也可以使用普通的HashMap。

@James的建議讓我開始思考是否可以通過調整不需要的並發來提高ConcurrentHashMap的速度。 它應該減少內存,但是您需要有成千上萬個這樣的內存才能發揮很大的作用。 因此,我編寫了此測試,似乎並不總是需要調整並發級別。

warmup: Average access time 36 ns.
warmup2: Average access time 28 ns.
1 concurrency: Average access time 25 ns.
2 concurrency: Average access time 25 ns.
4 concurrency: Average access time 25 ns.
8 concurrency: Average access time 25 ns.
16 concurrency: Average access time 24 ns.
32 concurrency: Average access time 25 ns.
64 concurrency: Average access time 26 ns.
128 concurrency: Average access time 26 ns.
256 concurrency: Average access time 26 ns.
512 concurrency: Average access time 27 ns.
1024 concurrency: Average access time 28 ns.

    public static void main(String[] args) {
    test("warmup", new ConcurrentHashMap());
    test("warmup2", new ConcurrentHashMap());
    for(int i=1;i<=1024;i+=i)
    test(i+" concurrency", new ConcurrentHashMap(16, 0.75f, i));
}

private static void test(String description, ConcurrentHashMap map) {
    Integer[] ints = new Integer[2000];
    for(int i=0;i<ints.length;i++)
        ints[i] = i;
    long start = System.nanoTime();
    for(int i=0;i<20*1000*1000;i+=ints.length) {
        for (Integer j : ints) {
            map.put(j,1);
            map.get(j);
        }
    }
    long time = System.nanoTime() - start;
    System.out.println(description+": Average access time "+(time/20/1000/1000/2)+" ns.");
}

正如@bestss指出的那樣,較大的並發級別可能較慢,因為它具有較差的緩存特性。

編輯:@betsss進一步關注如果沒有方法調用,循環是否會得到優化。 這是三個循環,所有循環都相同,但迭代次數不同。 他們打印

10M: Time per loop 661 ps.
100K: Time per loop 26490 ps.
1M: Time per loop 19718 ps.
10M: Time per loop 4 ps.
100K: Time per loop 17 ps.
1M: Time per loop 0 ps.

{
    int loops = 10*1000 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("10M: Time per loop "+1000*time/loops+" ps.");
}
{
    int loops = 100 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("100K: Time per loop "+1000*time/loops+" ps.");
}
{
    int loops = 1000 * 1000;
    long product = 1;
    long start = System.nanoTime();
    for(int i=0;i< loops;i++)
        product *= i;
    long time = System.nanoTime() - start;
    System.out.println("1M: Time per loop "+1000*time/loops+" ps.");
}
// code for three loops repeated

這就足夠了,因為ConcurrentHashMap的目的是允許無鎖的獲取/放置操作,但是請確保您以正確的並發級別使用它。 從文檔:

Ideally, you should choose a value to accommodate as many threads as will ever concurrently modify the table. Using a significantly higher value than you need can waste space and time, and a significantly lower value can lead to thread contention. But overestimates and underestimates within an order of magnitude do not usually have much noticeable impact. A value of one is appropriate when it is known that only one thread will modify and all others will only read. Also, resizing this or any other kind of hash table is a relatively slow operation, so, when possible, it is a good idea to provide estimates of expected table sizes in constructors.

請參閱http://download.oracle.com/javase/6/docs/api/java/util/concurrent/ConcurrentHashMap.html

編輯:

包裝的getter / setter沒什么區別,只要它仍被多個線程讀寫。 可以同時鎖定整個地圖,但這違反了使用ConcurrentHashMap的目的。

對於涉及大量寫入操作而較少讀取操作的情況, ConcurrentHashMap是一個很好的解決方案。 不利的一面是不能保證讀者在任何特定時刻看到的內容。 因此,如果您要求讀者查看地圖的最新版本,則不是一個好的解決方案。

Java 6 API文檔中:

檢索操作(包括get)通常不會阻塞,因此可能與更新操作(包括put和remove)重疊。 檢索反映了自發生以來最新完成的更新操作的結果。 對於諸如putAll和clear的聚合操作,並發檢索可能僅反映某些條目的插入或刪除。

如果這對於您的項目不可接受,則最好的解決方案實際上是完全同步鎖定。 據我所知,許多寫入操作和很少的讀取操作的解決方案會折衷最新的讀取操作,以實現更快的無阻塞寫入。 如果確實采用此解決方案,則Collections.synchronizedMap(...)方法將為任何地圖對象創建完全同步的單個讀取器/寫入器包裝器。 比編寫您自己的更加容易。

使用ConcurrentHashMap會更好,因為它的實現通常不會阻止讀取。 如果從外部進行同步,則最終將阻止大多數讀取,因為您無權訪問impl的內部知識。 不需要這樣做。

如果只有一位作者,那么使用ConcurrentHashMap應該是安全的。 如果您認為需要同步,則可以使用其他HashMap為您執行同步,並且比手動編寫同步要快。

是的...為了更好地對其進行優化,應將並發級別設置為1。

從Javadoc:

更新操作之間允許的並發性由可選的concurrencyLevel構造函數參數(默認為16)指導,該參數用作內部大小調整的提示。 ....當已知只有一個線程將修改而所有其他線程將僅讀取時,將值設為1是適當的。

該解決方案之所以有效,是因為對ConcurrentMaps的內存一致性影響:與其他並發集合一樣,在將對象作為鍵或值放入ConcurrentMap中之前,線程中的操作在訪問或從ConcurrentMap中刪除該對象之后發生。在另一個線程中。

暫無
暫無

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

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