HashMap vs. ConcurrentHashMap vs. SynchronizedMap – Cum poate fi sincronizat un HashMap în Java
Publicat: 2015-01-29
HashMap
este o structură de date foarte puternică în Java. Îl folosim zilnic și aproape în toate aplicațiile. Există destul de multe exemple pe care le-am scris înainte despre Cum să implementez cacheul Threadsafe, Cum să convertesc Hashmap în Arraylist?
Am folosit Hashmap în ambele exemple de mai sus, dar acestea sunt cazuri de utilizare destul de simple ale Hashmap. HashMap is a non-synchronized
.
Aveți vreo întrebare de mai jos?
- Care este diferența dintre ConcurrentHashMap și Collections.synchronizedMap(Map)?
- Care este diferența dintre ConcurrentHashMap și Collections.synchronizedMap(Map) în ceea ce privește performanța?
- ConcurrentHashMap vs Collections.synchronizedMap()
- Întrebări populare de interviu HashMap și ConcurrentHashMap
În acest tutorial vom trece peste toate interogările de mai sus și vom explica why and how
am putea sincroniza Hashmap?
De ce?
Obiectul Map este un container asociativ care stochează elemente, format dintr-o combinație între o key
de identificare unică și o value
mapată. Dacă aveți o aplicație foarte concurentă în care doriți să modificați sau să citiți valoarea cheii în fire diferite, atunci este ideal să utilizați Concurrent Hashmap. Cel mai bun exemplu este Producer Consumer, care se ocupă de citirea/scrierea simultană.
Deci, ce înseamnă harta thread-safe? Dacă multiple threads
accesează simultan o hartă hash și cel puțin unul dintre fire modifică structura structural, aceasta must be synchronized externally
pentru a evita o vizualizare inconsistentă a conținutului.
Cum?
Există două moduri în care putem sincroniza HashMap
- Metoda Java Collections synchronizedMap().
- Utilizați 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
- Ar trebui să utilizați ConcurrentHashMap atunci când aveți nevoie de concurență foarte mare în proiectul dvs.
- Este thread safe fără a sincroniza
whole map
. - Citirile se pot întâmpla foarte repede în timp ce scrierea se face cu o blocare.
- Nu există blocare la nivel de obiect.
- Blocarea este la o granularitate mult mai fină la nivel de găleată hashmap.
- ConcurrentHashMap nu lansează o
ConcurrentModificationException
dacă un fir încearcă să-l modifice în timp ce altul repetă peste el. - ConcurrentHashMap folosește o multitudine de blocări.
SynchronizedHashMap
- Sincronizare la nivel de obiect.
- Fiecare operație de citire/scriere trebuie să obțină blocare.
- Blocarea întregii colecții este o suprasolicitare a performanței.
- Acest lucru oferă, în esență, acces la un singur fir la întreaga hartă și blochează toate celelalte fire.
- Poate provoca dispute.
- SynchronizedHashMap returnează
Iterator
, care eșuează rapid la modificarea concomitentă.
Acum să aruncăm o privire la cod
- Creați clasa
CrunchifyConcurrentHashMapVsSynchronizedHashMap.java
- Creați obiect pentru fiecare HashTable, SynchronizedMap și CrunchifyConcurrentHashMap
- Adăugați și preluați 500.000 de intrări de pe Hartă
- Măsurați ora de început și de sfârșit și timpul de afișare în milisecunde
- Vom folosi ExecutorService pentru a rula
5 threads
în paralel
Iată un cod 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()
înseamnă că serviciul executor nu mai preia sarcinile primite.-
awaitTermination()
este invocat după o solicitare de oprire.
Și, prin urmare, trebuie să închideți mai întâi serviceExecutor și apoi să blocați și să așteptați ca firele să se termine.
Rezultat consola 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 |