1
votes

synchroniser une méthode en obtenant de meilleures performances?

J'ai une classe qui est appelée par plusieurs threads sur une machine multicœur. Je veux le rendre sûr pour les threads. La méthode

add sera appelée par plusieurs threads. Et si la clé existe, ajoutez simplement la valeur actuelle à la nouvelle valeur, sinon mettez simplement la clé et la valeur dans la carte.

Maintenant, pour le rendre sûr, je prévoyais de synchroniser add mais cela détruira les performances. Existe-t-il un meilleur moyen d'obtenir de meilleures performances sans synchroniser la méthode add ?

class Test {
  private final Map<Integer, Integer> map = new ConcurrentHashMap<>();

  public void add(int key, int value) {
    if (map.containsKey(key)) {
      int val = map.get(key);
      map.put(key, val + value);
      return;
    }
    map.put(key, value);
  }

  public Object getResult() {
    return map.toString();
  }
}


1 commentaires

3 Réponses :


4
votes

mais cela détruira les performances

Cela ne détruirait pas les performances. Cela le réduira un peu, avec une réduction supplémentaire s'il y a un taux de collision élevé.

Existe-t-il un meilleur moyen d'obtenir de meilleures performances?

Oui, utilisez merge () (Java 8+). Citant le javadoc:

Si la clé spécifiée n'est pas déjà associée à une valeur ou est associée à null, l'associe à la valeur non nulle donnée. Sinon, remplace la valeur associée par les résultats de la fonction de remappage donnée, ou supprime si le résultat est nul.

Exemple:

public void add(int key, int value) {
    map.merge(key, value, Integer::sum);
}

Ou en utilisant une référence de méthode à sum (int a, int b) au lieu d'une expression lambda :

public void add(int key, int value) {
    map.merge(key, value, (a, b) -> a + b);
}

2 commentaires

Comment pouvons-nous faire cela dans Java 7? Y a-t-il un moyen de le faire?


@flash Dans Java 7, vous le faites avec synchronized (encore une autre bonne raison de mettre à niveau).



3
votes

Utilisez merge :

class Test {
    final Map<Integer, AtomicInteger> map = new ConcurrentHashMap<>();

    public void add(int key, int value) {
        get(key).addAndGet(value);
    }

    private AtomicInteger get(int key) {
        AtomicInteger current = map.get(key);

        if (current == null) {
            AtomicInteger ai = new AtomicInteger();

            current = map.putIfAbsent(key, ai);

            if (current == null) {
                current = ai;
            }
        }

        return current;
    }

    public Object getResult() {
        return map.toString();
    }
}

Solution Java 7 si vous ne pouvez absolument pas utiliser synchronized (ou si vous ne pouvez absolument pas verrouiller explicitement):

class Test {
    final Map<Integer, Integer> map = new ConcurrentHashMap<>();

    public void add(int key, int value) {
        map.merge(key, value, Integer::sum);
    }

    public Object getResult() {
        return map.toString();
    }
}


4 commentaires

que fait la fusion dans les coulisses? Et si nous n'avons pas besoin d'utiliser la fusion, comment pouvons-nous le faire à l'ancienne?


github. com / frohoff / jdk8u-jdk / blob / master / src / share / classes / j‌ ava /… il utilise CAS. "Pas besoin d'utiliser la fusion" -> "Y a-t-il un meilleur moyen par lequel nous pouvons obtenir de meilleures performances sans synchroniser la méthode d'ajout?". C'est comme ça que tu le fais


Ouais d'accord, mais je demandais au lieu d'utiliser la fonction de fusion, pouvons-nous le faire également dans Java 7 à l'ancienne?


Si vous êtes décidé à ne pas utiliser de look synchronisé ou explicite, consultez la rubrique Modifier la réponse.



1
votes

synchronized ne provoque un goulot d'étranglement que lorsque vous exécutez une opération coûteuse tenant un verrou.
Dans votre cas en ajoutant un synchronisé que vous faites:
1. vérifier un hashmap pour l'existence d'une clé
2. obtenir la valeur mappée à cette clé
3. faites un ajout et remettez le résultat dans la table de hachage.

Toutes ces opérations sont très bon marché O (1) et à moins que vous n'utilisiez un modèle étrange pour les clés qui sont des entiers, il est très peu probable que vous puissiez obtenir des performances dégénérées en raison de collisions.

Je suggérerais si vous ne pouvez pas utiliser merge comme le soulignent les autres réponses, de simplement synchroniser . Vous devriez être tellement considéré comme des performances uniquement dans les hotpaths critiques et après avoir réellement profilé qu'il y a un problème là-bas


0 commentaires