HashMap 對比ConcurrentHashMap 對比SynchronizedMap – 如何在 Java 中同步 HashMap
已發表: 2015-01-29
HashMap
是 Java 中非常強大的數據結構。 我們每天都在使用它,幾乎在所有應用程序中都使用它。 我之前寫過很多例子,關於如何實現線程安全緩存,如何將 Hashmap 轉換為 Arraylist?
我們在上面的兩個示例中都使用了 Hashmap,但這些都是非常簡單的 Hashmap 用例。 HashMap is a non-synchronized
集合類。
您有以下任何問題嗎?
- ConcurrentHashMap 和 Collections.synchronizedMap(Map) 有什麼區別?
- ConcurrentHashMap 和 Collections.synchronizedMap(Map) 在性能方面有什麼區別?
- ConcurrentHashMap 與 Collections.synchronizedMap()
- 熱門 HashMap 和 ConcurrentHashMap 面試題
在本教程中,我們將介紹所有上述查詢以及why and how
同步 Hashmap?
為什麼?
Map 對像是存儲元素的關聯容器,由唯一標識的key
和映射的value
組合而成。 如果您有非常高並發的應用程序,您可能希望在不同的線程中修改或讀取鍵值,那麼使用 Concurrent Hashmap 是理想的。 最好的例子是處理並發讀/寫的生產者消費者。
那麼線程安全的 Map 是什麼意思呢? 如果multiple threads
同時訪問一個哈希映射,並且至少有一個線程在結構上修改了映射,則must be synchronized externally
以避免內容視圖不一致。
如何?
有兩種方法可以同步 HashMap
- Java 集合 synchronizedMap() 方法
- 使用 ConcurrentHashMap
1 2 3 4 5 6 7 8 |
//Hashtable Map < String , String > normalMap = new Hashtable < String , String > ( ) ; //synchronizedMap synchronizedHashMap = Collections . synchronizedMap ( new HashMap < String , String > ( ) ) ; //ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap < String , String > ( ) ; |
並發哈希映射
- 當您的項目需要非常高的並發性時,您應該使用 ConcurrentHashMap。
- 它是線程安全的,無需同步
whole map
。 - 使用鎖完成寫入時,讀取可能會非常快。
- 在對象級別沒有鎖定。
- 鎖定在哈希圖存儲桶級別的粒度要細得多。
- 如果一個線程嘗試修改它而另一個線程正在對其進行迭代,則 ConcurrentHashMap 不會引發
ConcurrentModificationException
。 - ConcurrentHashMap 使用大量鎖。
同步HashMap
- 對象級別的同步。
- 每個讀/寫操作都需要獲取鎖。
- 鎖定整個集合是一種性能開銷。
- 這實質上只允許訪問整個映射的一個線程並阻止所有其他線程。
- 它可能會引起爭用。
- SynchronizedHashMap 返回
Iterator
,它在並發修改時快速失敗。
現在讓我們看一下代碼
- 創建類
CrunchifyConcurrentHashMapVsSynchronizedHashMap.java
- 為每個 HashTable、SynchronizedMap 和 CrunchifyConcurrentHashMap 創建對象
- 從 Map 添加和檢索 500k 條目
- 測量開始和結束時間並以毫秒為單位顯示時間
- 我們將使用 ExecutorService 並行運行
5 threads
這是一個Java代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
package crunchify . com . tutorials ; import java . util . Collections ; import java . util . HashMap ; import java . util . Hashtable ; import java . util . Map ; import java . util . concurrent . ConcurrentHashMap ; import java . util . concurrent . ExecutorService ; import java . util . concurrent . Executors ; import java . util . concurrent . TimeUnit ; /** * @author Crunchify.com * */ public class CrunchifyConcurrentHashMapVsSynchronizedMap { public final static int THREAD_POOL_SIZE = 5 ; public static Map < String , Integer > crunchifyHashTableObject = null ; public static Map < String , Integer > crunchifySynchronizedMapObject = null ; public static Map < String , Integer > crunchifyConcurrentHashMapObject = null ; public static void main ( String [ ] args ) throws InterruptedException { // Test with Hashtable Object crunchifyHashTableObject = new Hashtable < String , Integer > ( ) ; crunchifyPerformTest ( crunchifyHashTableObject ) ; // Test with synchronizedMap Object crunchifySynchronizedMapObject = Collections . synchronizedMap ( new HashMap < String , Integer > ( ) ) ; crunchifyPerformTest ( crunchifySynchronizedMapObject ) ; // Test with ConcurrentHashMap Object crunchifyConcurrentHashMapObject = new ConcurrentHashMap < String , Integer > ( ) ; crunchifyPerformTest ( crunchifyConcurrentHashMapObject ) ; } public static void crunchifyPerformTest ( final Map < String , Integer > crunchifyThreads ) throws InterruptedException { System . out . println ( "Test started for: " + crunchifyThreads . getClass ( ) ) ; long averageTime = 0 ; for ( int i = 0 ; i < 5 ; i ++ ) { long startTime = System . nanoTime ( ) ; ExecutorService crunchifyExServer = Executors . newFixedThreadPool ( THREAD_POOL_SIZE ) ; for ( int j = 0 ; j < THREAD_POOL_SIZE ; j ++ ) { crunchifyExServer . execute ( new Runnable ( ) { @SuppressWarnings ( "unused" ) @Override public void run ( ) { for ( int i = 0 ; i < 500000 ; i ++ ) { Integer crunchifyRandomNumber = ( int ) Math . ceil ( Math . random ( ) * 550000 ) ; // Retrieve value. We are not using it anywhere Integer crunchifyValue = crunchifyThreads . get ( String . valueOf ( crunchifyRandomNumber ) ) ; // Put value crunchifyThreads . put ( String . valueOf ( crunchifyRandomNumber ) , crunchifyRandomNumber ) ; } } } ) ; } // Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted. Invocation // has no additional effect if already shut down. // This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that. crunchifyExServer . shutdown ( ) ; // Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is // interrupted, whichever happens first. crunchifyExServer . awaitTermination ( Long . MAX_VALUE , TimeUnit . DAYS ) ; long entTime = System . nanoTime ( ) ; long totalTime = ( entTime - startTime ) / 1000000L ; averageTime += totalTime ; System . out . println ( "500K entried added/retrieved in " + totalTime + " ms" ) ; } System . out . println ( "For " + crunchifyThreads . getClass ( ) + " the average time is " + averageTime / 5 + " ms\n" ) ; } } |

shutdown()
表示執行器服務不再接受傳入任務。-
awaitTermination()
在關閉請求後調用。
因此,您需要先關閉 serviceExecutor,然後阻塞並等待線程完成。
Eclipse 控制台結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Test started for: class java.util.Hashtable 500K entried added/retrieved in 1832 ms 500K entried added/retrieved in 1723 ms 500K entried added/retrieved in 1782 ms 500K entried added/retrieved in 1607 ms 500K entried added/retrieved in 1851 ms For class java.util.Hashtable the average time is 1759 ms Test started for: class java.util.Collections$SynchronizedMap 500K entried added/retrieved in 1923 ms 500K entried added/retrieved in 2032 ms 500K entried added/retrieved in 1621 ms 500K entried added/retrieved in 1833 ms 500K entried added/retrieved in 2048 ms For class java.util.Collections$SynchronizedMap the average time is 1891 ms Test started for: class java.util.concurrent.ConcurrentHashMap 500K entried added/retrieved in 1068 ms 500K entried added/retrieved in 1029 ms 500K entried added/retrieved in 1165 ms 500K entried added/retrieved in 840 ms 500K entried added/retrieved in 1017 ms For class java.util.concurrent.ConcurrentHashMap the average time is 1023 ms |