web-dev-qa-db-fra.com

Un ConcurrentHashMap doit-il être encapsulé dans un bloc synchronisé?

Toutes les opérations non-récupération sur un ConcurrentHashMap (put(), remove() etc.) doivent-elles être encapsulées dans un bloc synchronized(this)? Je comprends que toutes ces opérations sont thread-safe, donc y a-t-il un réel avantage/besoin à le faire? Les seules opérations utilisées sont put() et remove().

protected final Map<String, String> mapDataStore = new ConcurrentHashMap<String, String>();

public void updateDataStore(final String key, final String value) {
    ...
    synchronized (this) {
        mapDataStore.put(key, value);
    }
    ...
}
31
MeanwhileInHell

Non, vous perdez les avantages de ConcurrentHashMap en faisant cela. Vous pouvez également utiliser un HashMap avec synchronized ou synchronizedMap() pour verrouiller la table entière (ce que vous faites lorsque vous encapsulez des opérations dans synchronized, car le moniteur implicite est l'instance d'objet entière.)

Le but de ConcurrentHashMap est d'augmenter le débit de votre code simultané en autorisant la lecture/écriture simultanée sur la table sans verrouiller la table entière. Le tableau prend en charge cela en interne en utilisant la répartition des verrous (plusieurs verrous au lieu d'un, chaque verrou étant attribué à un ensemble de compartiments de hachage - voir Java Concurrency in Practice par Goetz et al).

Une fois que vous utilisez ConcurrentHashMap, toutes les méthodes de mappage standard (put(), remove(), etc.) deviennent atomiques en raison de la bande de verrouillage, etc. dans l'implémentation. Les seuls compromis sont que des méthodes comme size() et isEmpty() peuvent ne pas nécessairement renvoyer des résultats précis, car la seule façon de le faire serait que toutes les opérations verrouillent la table entière.

L'interface ConcurrentMap ajoute également de nouvelles opérations de composés atomiques comme putIfAbsent() (ne mettre quelque chose que si la clé n'est pas déjà dans la carte), remove() accepter à la fois la clé et la valeur (supprimer une entrée uniquement si sa valeur est égale à un paramètre que vous passez), etc. Ces opérations nécessitaient auparavant de verrouiller la table entière car elles nécessitaient deux appels de méthode (par exemple putIfAbsent() aurait besoin appels à la fois containsKey() et put(), encapsulés dans un bloc synchronized, si vous utilisiez une implémentation standard Map.) Encore une fois, vous obtenez un débit supérieur en utilisant ces méthodes, en évitant de verrouiller la table entière.

52
sparc_spread

La synchronisation de ces opérations n'a aucun avantage ici - elle dégrade réellement les performances si vous n'avez pas besoin de synchronisation.

La raison pour laquelle ConcurrentHashMap a été créé, est que les cartes synchronisées (implémentées à la main comme dans la question ou instanciées de la manière habituelle avec Collections.synchronizedMap(map)) affichent de mauvaises performances lorsqu'elles sont accessibles par de nombreux threads. Les opérations put et get sont bloquantes, donc tous les autres threads doivent attendre et ne peuvent pas accéder simultanément à la carte. Le ConcurrentHashMap - comme son nom l'indique - permet d'autre part un accès simultané. Vous perdez cet avantage si vous ajoutez la synchronisation.

10
kapex