3 votes

Est-ce qu'une attribution à l'intérieur de ConcurrentHashMap.computeIfAbsent est thread-safe?

Considérez l'implémentation suivante de quelque type de cache de taille fixe, qui permet la recherche par une poignée entière:

static class HandleCache {
    private final AtomicInteger counter = new AtomicInteger();
    private final Map handles = new ConcurrentHashMap<>();
    private final Data[] array = new Data[100_000];

    int getHandle(Data data) {
        return handles.computeIfAbsent(data, k -> {
            int i = counter.getAndIncrement();
            if (i >= array.length) {
                throw new IllegalStateException("débordement du tableau");
            }
            array[i] = data;
            return i;
        });

    }

    Data getData(int handle) {
        return array[handle];
    }
}

Il y a un magasin de tableau à l'intérieur de la fonction de calcul, qui n'est synchronisé d'aucune manière. Serait-il autorisé par le modèle mémoire de Java pour d'autres threads de lire une valeur nulle dans ce tableau plus tard?

PS: Est-ce que le résultat changerait si l'id retourné de getHandle était stocké dans un champ final et seulement accédé à travers ce champ par d'autres threads?

1voto

Peter Lawrey Points 229686

L'accès en lecture n'est pas sûr pour le multithreading. Vous pourriez le rendre sûr pour le multithreading de manière indirecte, cependant il est susceptible d'être fragile. Je l'implémenterais d'une manière beaucoup plus simple et n'optimiserait que plus tard s'il s'avère être un problème de performance, par exemple si vous le observez dans un profileur pour un test réaliste.

static class HandleCache {
    private final Map handles = new HashMap<>();
    private final List dataByIndex = new ArrayList<>();

    synchronized int getHandle(Data data) {
        Integer id = handles.get(data);
        if (id == null) {
             id = handles.size();
             handles.put(data, id);
             dataByIndex.add(id);
        }
        return id;
    }

    synchronized Data getData(int handle) {
        return dataByIndex.get(handle);
    }
}

1voto

David Soroko Points 1496

En supposant que vous déterminez l'index pour le tableau lu à partir de la valeur de counter, alors oui - vous pouvez obtenir une lecture nulle

L'exemple le plus simple (il y en a d'autres) est le suivant :

T1 appelle getHandle(data) et est suspendu juste après int i = counter.getAndIncrement(); T2 appelle handles[counter.get()] et lit null.

Vous devriez pouvoir vérifier cela facilement en plaçant stratégiquement un sleep et en utilisant deux threads.

0voto

Jacob G. Points 16099

Depuis la documentation de ConcurrentHashMap#computeIfAbsent:

L'ensemble de l'invocation de la méthode est effectué atomiquement, donc la fonction est appliquée au maximum une fois par clé. Certaines opérations de mise à jour tentées sur cette carte par d'autres threads peuvent être bloquées pendant que le calcul est en cours, alors le calcul doit être court et simple, et ne doit pas tenter de mettre à jour d'autres mappages de cette carte.

La référence de la documentation au blocage ne concerne que les opérations de mise à jour sur la Map, donc si un autre thread tente d'accéder directement à array (plutôt que par une opération de mise à jour sur la Map), il peut y avoir des conditions de concurrence et null peut être lu.

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