69 votes

Faut-il vérifier si la carte contientKey avant d'utiliser la fonction putIfAbsent de ConcurrentMap ?

J'ai utilisé ConcurrentMap de Java pour créer une carte qui peut être utilisée à partir de plusieurs threads. La méthode putIfAbsent est excellente et beaucoup plus facile à lire et à écrire que les opérations standard de la carte. J'ai du code qui ressemble à ceci :

ConcurrentMap<String, Set<X>> map = new ConcurrentHashMap<String, Set<X>>();

// ...

map.putIfAbsent(name, new HashSet<X>());
map.get(name).add(Y);

La lisibilité est excellente, mais il faut créer un nouveau HashSet à chaque fois, même s'il est déjà dans la carte. Je pourrais écrire ceci :

if (!map.containsKey(name)) {
    map.putIfAbsent(name, new HashSet<X>());
}
map.get(name).add(Y);

Avec ce changement, il perd un peu en lisibilité mais n'a pas besoin de créer le HashSet à chaque fois. Qu'est-ce qui est le mieux dans ce cas ? J'ai tendance à me ranger du côté de la première solution, car elle est plus lisible. La seconde serait plus performante et peut-être plus correcte. Il existe peut-être une meilleure façon de procéder que l'une ou l'autre.

Quelle est la meilleure pratique pour utiliser un putIfAbsent de cette manière ?

105voto

Tom Hawtin - tackline Points 82671

La concussion est difficile. Si vous devez vous embêter avec des cartes concurrentes au lieu d'un simple verrouillage, vous pouvez aussi bien le faire. En effet, ne faites pas de recherches plus que nécessaire.

Set<X> set = map.get(name);
if (set == null) {
    final Set<X> value = new HashSet<X>();
    set = map.putIfAbsent(name, value);
    if (set == null) {
        set = value;
    }
}

(Clause de non-responsabilité habituelle de stackoverflow : ce que j'ai en tête. Pas testé. Pas compilé. Etc.)

Mise à jour : 1.8 a ajouté computeIfAbsent méthode par défaut pour ConcurrentMap (et Map ce qui est assez intéressant car cette implémentation serait fausse pour ConcurrentMap ). (Et la version 1.7 a ajouté l'opérateur "diamant". <> .)

Set<X> set = map.computeIfAbsent(name, n -> new HashSet<>());

(Remarque : vous êtes responsable de la sécurité des fils de toutes les opérations de l'application HashSet contenues dans le ConcurrentMap .)

16voto

Jed Wesley-Smith Points 3094

La réponse de Tom est correcte en ce qui concerne l'utilisation de l'API pour ConcurrentMap. Une alternative qui évite d'utiliser putIfAbsent est d'utiliser la carte de calcul de GoogleCollections/Guava MapMaker qui remplit automatiquement les valeurs avec une fonction fournie et gère toute la sécurité des fils pour vous. Elle ne crée en fait qu'une seule valeur par clé et si la fonction de création est coûteuse, les autres threads demandant la même clé se bloqueront jusqu'à ce que la valeur devienne disponible.

Editar à partir de Guava 11, MapMaker est déprécié et remplacé par le matériel Cache/LocalCache/CacheBuilder. C'est un peu plus compliqué dans son utilisation mais fondamentalement isomorphe.

5voto

Craig P. Motlin Points 11814

Vous pouvez utiliser MutableMap.getIfAbsentPut(K, Function0<? extends V>) de Collections Eclipse (anciennement Collections GS ).

L'avantage par rapport à l'appel get() en effectuant un contrôle de nullité, puis en appelant putIfAbsent() est que nous ne calculons le code de hachage de la clé qu'une seule fois, et que nous ne trouvons le bon endroit dans la table de hachage qu'une seule fois. Dans les cartes simultanées comme org.eclipse.collections.impl.map.mutable.ConcurrentHashMap la mise en œuvre de getIfAbsentPut() est également sûr pour les fils et atomique.

import org.eclipse.collections.impl.map.mutable.ConcurrentHashMap;
...
ConcurrentHashMap<String, MyObject> map = new ConcurrentHashMap<>();
map.getIfAbsentPut("key", () -> someExpensiveComputation());

La mise en œuvre de org.eclipse.collections.impl.map.mutable.ConcurrentHashMap est vraiment non bloquante. Bien que tous les efforts soient faits pour ne pas appeler la fonction d'usine inutilement, il y a toujours une chance qu'elle soit appelée plus d'une fois pendant la contention.

C'est ce qui le différencie de Java 8. ConcurrentHashMap.computeIfAbsent(K, Function<? super K,? extends V>) . La Javadoc de cette méthode indique :

L'invocation complète de la méthode est effectuée de manière atomique, de sorte que la fonction est appliquée au maximum une fois par clé. Certaines tentatives d'opérations de mise à jour sur sur cette carte par d'autres threads peuvent être bloquées pendant le calcul calcul est en cours, donc le calcul doit être court et simple...

Note : Je suis un committer pour Eclipse Collections.

3voto

karmakaze Points 4615

En gardant une valeur pré-initialisée pour chaque thread, vous pouvez améliorer la réponse acceptée :

Set<X> initial = new HashSet<X>();
...
Set<X> set = map.putIfAbsent(name, initial);
if (set == null) {
    set = initial;
    initial = new HashSet<X>();
}
set.add(Y);

Je l'ai récemment utilisé avec des valeurs de carte AtomicInteger plutôt que Set.

0voto

ggrandes Points 578

Mon approximation générique :

public class ConcurrentHashMapWithInit<K, V> extends ConcurrentHashMap<K, V> {
  private static final long serialVersionUID = 42L;

  public V initIfAbsent(final K key) {
    V value = get(key);
    if (value == null) {
      value = initialValue();
      final V x = putIfAbsent(key, value);
      value = (x != null) ? x : value;
    }
    return value;
  }

  protected V initialValue() {
    return null;
  }
}

Et comme exemple d'utilisation :

public static void main(final String[] args) throws Throwable {
  ConcurrentHashMapWithInit<String, HashSet<String>> map = 
        new ConcurrentHashMapWithInit<String, HashSet<String>>() {
    private static final long serialVersionUID = 42L;

    @Override
    protected HashSet<String> initialValue() {
      return new HashSet<String>();
    }
  };
  map.initIfAbsent("s1").add("chao");
  map.initIfAbsent("s2").add("bye");
  System.out.println(map.toString());
}

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