Java Language Opérations atomiques


Exemple

Une opération atomique est une opération exécutée "tout à la fois", sans aucune chance que d'autres threads observent ou modifient l'état pendant l'exécution de l'opération atomique.

Considérons un mauvais exemple .

private static int t = 0;

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            t++;
            System.out.println(MessageFormat.format("t: {0}", t));
        });
    }
    executorService.shutdown();
}

Dans ce cas, il y a deux problèmes. Le premier problème est que l'opérateur de post-incrémentation n'est pas atomique. Il est composé de plusieurs opérations: obtenir la valeur, ajouter 1 à la valeur, définir la valeur. C'est pourquoi si nous lançons l'exemple, il est probable que nous ne verrons pas t: 100 dans la sortie - deux threads peuvent obtenir simultanément la valeur, l'incrémenter et la définir: disons que la valeur de t est 10 et deux les threads incrémentent t. Les deux threads définiront la valeur de t à 11, puisque le deuxième thread observe la valeur de t avant que le premier thread ne l'ait fini.

Le deuxième problème concerne la façon dont nous observons t. Lorsque nous imprimons la valeur de t, la valeur peut avoir déjà été modifiée par un thread différent après l'opération d'incrémentation de ce thread.

Pour résoudre ces problèmes, nous utiliserons le java.util.concurrent.atomic.AtomicInteger , qui a plusieurs opérations atomiques à utiliser.

private static AtomicInteger t = new AtomicInteger(0);

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            int currentT = t.incrementAndGet();
            System.out.println(MessageFormat.format("t: {0}", currentT));
        });
    }
    executorService.shutdown();
}

La méthode incrementAndGet d' AtomicInteger incrémente et retourne la nouvelle valeur, éliminant ainsi la condition de course précédente. Veuillez noter que dans cet exemple, les lignes seront toujours hors service car nous ne faisons aucun effort pour séquencer les appels println et que cela sort du cadre de cet exemple, car cela nécessiterait une synchronisation et l'objectif de cet exemple est de montrer comment utiliser AtomicInteger pour éliminer les conditions de course concernant l’état.