85 votes

Java synchronisé bloc vs Collections.synchronizedMap

Est le code suivant configuré pour synchroniser correctement les appels sur synchronizedMap?

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

De ma compréhension, j'ai besoin de la synchronisation de bloc en addToMap() pour éviter un autre thread à partir de l'appelant remove() ou containsKey() avant que je reçois à travers l'appel à put() mais je n'ai pas besoin d'un bloc synchronisé en doWork() parce qu'un autre thread ne peut entrer dans le bloc synchronisé en addToMap() avant remove() retourne parce que j'ai créé la Carte à l'origine avec l' Collections.synchronizedMap(). Est-ce exact? Est-il une meilleure façon de le faire?

90voto

Yuval Adam Points 59423

Collections.synchronizedMap() garantit que chaque atomique de l'opération que vous souhaitez exécuter sur la carte seront synchronisés.

L'exécution de deux (ou plus) les opérations sur la carte, cependant, doit être synchronisée dans un bloc. Alors oui - vous se synchronisent correctement.

15voto

TofuBeer Points 32441

Si vous utilisez le JDK 6, alors vous pourriez vouloir vérifier ConcurrentHashMap

Remarque le putIfAbsent méthode dans cette classe.

13voto

JLR Points 800

Il y a le potentiel pour un subtil bug dans votre code.

[Mise à JOUR: Depuis qu'il est à l'aide de la carte.remove() cette description n'est pas totalement valide. J'ai raté qui fait la première fois à travers. :( Merci à la question de l'auteur pour le pointage. Je suis en laissant le reste comme c'est, mais il a changé le rapport de fil pour dire qu'il y a potentiellement un bug.]

Dans doWork() vous obtenez la Liste de la valeur de la Carte dans un thread-safe. Par la suite, cependant, vous accédez à cette liste dans une dangereuse affaire. Par exemple, un thread peut être à l'aide de la liste dans doWork() , tandis qu'un autre thread appelle synchronizedMap.get(clé).ajouter(valeur) dans addToMap(). Ces deux accès ne sont pas synchronisés. La règle de base est qu'une collection est thread-safe garanties, ne s'étend pas à la remise des clés ou des valeurs qu'ils stockent.

Vous pouvez résoudre ce problème en insérant un synchronisé de la liste de la carte comme

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

Vous pouvez également synchroniser sur la carte pendant que vous accédez à la liste de doWork():

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

Enfin, la dernière option limite la simultanéité un peu, mais qui est un peu plus claire de l'OMI.

Aussi, une note rapide sur ConcurrentHashMap. C'est vraiment utile en classe, mais n'est pas toujours un remplacement approprié pour la synchronisation de la HashMaps. Citant sa Javadoc,

Cette classe est entièrement interopérable avec table de hachage dans les programmes qui s'appuient sur son fil de sécurité mais pas les détails de synchronisation.

En d'autres termes, putIfAbsent() est idéal pour l'insère, mais ne garantit pas d'autres parties de la carte ne change pas au cours de cet appel; il garantit seulement l'atomicité. Dans votre exemple de programme, vous êtes en s'appuyant sur les détails de synchronisation (synchronisé) HashMap pour d'autres choses que de mettre des ().

Dernière chose. :) Cette grande citation de Java de la Simultanéité dans la Pratique m'aide toujours dans la conception d'un débogage de programmes multi-thread.

Pour chaque mutable variable d'état qui peut être consulté par plus d'un thread, tous les accès à cette variable doit être effectuée avec la même verrou.

4voto

Paul Tomblin Points 83687

Qui semble correct pour moi. Si je devais changer quelque chose, je voudrais arrêter d'utiliser les Collections.synchronizedMap() et de tout synchroniser de la même façon, juste pour rendre les choses plus claires.

Aussi, je voudrais remplacer

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

avec

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

2voto

Barend Points 8232

Découvrez Google Collections' Multimap, par exemple, à la page 28 de cette présentation.

Si vous ne pouvez pas utiliser cette bibliothèque pour une raison quelconque, envisager l'utilisation d' ConcurrentHashMap au lieu de SynchronizedHashMap; il a une chouette putIfAbsent(K,V) méthode avec laquelle vous pouvez automatiquement ajouter l'élément de la liste si c'est pas déjà là. Aussi, envisager l'utilisation d' CopyOnWriteArrayList pour les valeurs de la carte si vos habitudes d'utilisation, mandat de le faire.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X