HashMap contre. ConcurrentHashMap Vs. SynchronizedMap - Comment un HashMap peut être synchronisé en Java
Publié: 2015-01-29
HashMap
est une structure de données très puissante en Java. Nous l'utilisons tous les jours et presque dans toutes les applications. Il y a pas mal d'exemples que j'ai déjà écrits sur Comment implémenter le cache Threadsafe, Comment convertir Hashmap en Arraylist ?
Nous avons utilisé Hashmap dans les deux exemples ci-dessus, mais ce sont des cas d'utilisation assez simples de Hashmap. HashMap is a non-synchronized
.
Avez-vous l'une des questions ci-dessous ?
- Quelle est la différence entre ConcurrentHashMap et Collections.synchronizedMap(Map) ?
- Quelle est la différence entre ConcurrentHashMap et Collections.synchronizedMap(Map) en termes de performances ?
- ConcurrentHashMap vs Collections.synchronizedMap()
- Questions d'entretien populaires sur HashMap et ConcurrentHashMap
Dans ce didacticiel, nous passerons en revue toutes les requêtes ci-dessus et expliquerons why and how
nous pourrions synchroniser Hashmap ?
Pourquoi?
L'objet Map est un conteneur associatif qui stocke des éléments, formé par une combinaison d'une key
d'identification unique et d'une value
mappée. Si vous avez une application très hautement simultanée dans laquelle vous souhaitez modifier ou lire la valeur de la clé dans différents threads, il est idéal d'utiliser Concurrent Hashmap. Le meilleur exemple est Producer Consumer qui gère la lecture/écriture simultanée.
Alors, que signifie la carte thread-safe ? Si multiple threads
accèdent simultanément à une carte de hachage et qu'au moins l'un des threads modifie la carte de manière structurelle, il must be synchronized externally
pour éviter une vue incohérente du contenu.
Comment?
Il y a deux façons de synchroniser HashMap
- Méthode Java Collections synchronizedMap()
- Utiliser 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 > ( ) ; |
ConcurrentHashMapConcurrentHashMap
- Vous devez utiliser ConcurrentHashMap lorsque vous avez besoin d'une simultanéité très élevée dans votre projet.
- Il est thread-safe sans synchroniser
whole map
. - Les lectures peuvent se produire très rapidement alors que l'écriture est effectuée avec un verrou.
- Il n'y a pas de verrouillage au niveau de l'objet.
- Le verrouillage est à une granularité beaucoup plus fine au niveau du compartiment de hashmap.
- ConcurrentHashMap ne lève pas d'exception
ConcurrentModificationException
si un thread essaie de le modifier pendant qu'un autre itère dessus. - ConcurrentHashMap utilise une multitude de verrous.
SynchronizedHashMapSynchronizedHashMap
- Synchronisation au niveau de l'objet.
- Chaque opération de lecture/écriture doit acquérir un verrou.
- Verrouiller l'ensemble de la collection est une surcharge de performance.
- Cela donne essentiellement accès à un seul thread à l'ensemble de la carte et bloque tous les autres threads.
- Cela peut provoquer des conflits.
- SynchronizedHashMap renvoie
Iterator
, qui échoue rapidement en cas de modification simultanée.
Voyons maintenant le code
- Créer la classe
CrunchifyConcurrentHashMapVsSynchronizedHashMap.java
- Créer un objet pour chaque HashTable, SynchronizedMap et CrunchifyConcurrentHashMap
- Ajouter et récupérer 500 000 entrées à partir de Map
- Mesurez l'heure de début et de fin et affichez l'heure en millisecondes
- Nous utiliserons ExecutorService pour exécuter
5 threads
en parallèle
Voici un code 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()
signifie que le service exécuteur ne prend plus de tâches entrantes.-
awaitTermination()
est invoqué après une demande d'arrêt.
Et par conséquent, vous devez d'abord arrêter le serviceExecutor, puis bloquer et attendre que les threads se terminent.
Résultat de la console 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 |