Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Apprendre Variables Atomiques | Mécanismes de Synchronisation de Haut Niveau
Multithreading en Java
course content

Contenu du cours

Multithreading en Java

Multithreading en Java

1. Notions de Base du Multithreading
2. Collections Synchronisées
3. Mécanismes de Synchronisation de Haut Niveau
4. Meilleures Pratiques de Multithreading

book
Variables Atomiques

Nous avons déjà couvert ce qu'est l'atomicité et les problèmes qu'elle peut causer dans la première section de ce cours. À l'époque, nous avons abordé le problème en utilisant des blocs synchronisés ou des méthodes. Maintenant, nous allons explorer comment obtenir le même résultat plus facilement en utilisant une classe atomique.

Qu'est-ce que les Variables Atomiques ?

Les variables atomiques garantissent que les opérations (lecture, écriture, incrément) sur les variables sont effectuées atomiquement, c'est-à-dire qu'elles sont exécutées de manière contiguë et en toute sécurité dans un environnement multithread. Cela garantit que l'opération sera complétée entièrement, sans possibilité d'interférence d'autres threads pendant son exécution.

Pourquoi avons-nous besoin de variables atomiques ?

Sans l'utilisation de variables atomiques ou d'autres mécanismes de synchronisation, des opérations comme l'incrémentation (++) peuvent être dangereuses. Par exemple, lorsque plusieurs threads accèdent à la même variable simultanément, les mises à jour peuvent être perdues, entraînant des résultats incorrects. Les variables atomiques résolvent ce problème en garantissant que les opérations sur elles sont exécutées séquentiellement.

Nous avons déjà discuté de ce problème lorsque l'opération d'incrémentation était décomposée en trois étapes (lecture, incrémentation, écriture), mais avec les variables atomiques, tout est fait en une seule opération !

Types de Variables Atomiques en Java

Remarque

En général, il existe de nombreuses implémentations atomiques, et nous ne les couvrirons pas toutes ici, car cela prendrait trop de temps.

Java fournit plusieurs classes de variables atomiques dans le package java.util.concurrent.atomic, chacune conçue pour gérer un type de données spécifique :

  • AtomicInteger : pour les opérations atomiques sur int ;
  • AtomicLong : pour les opérations atomiques sur long ;
  • AtomicBoolean : pour les opérations atomiques sur boolean ;
  • AtomicReference<V> : pour les opérations atomiques sur les objets (type générique).

Méthodes

La méthode get() retourne la valeur actuelle d'une variable. La méthode set(V newValue) définit une nouvelle valeur pour la variable. D'autre part, lazySet(V newValue) est similaire à set(), mais peut différer la mise à jour de la valeur, offrant une mise à jour ordonnée dans certaines situations.

java

Main

copy
123456789101112131415161718192021
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Using `get()` to retrieve the current value String value = atomicString.get(); System.out.println("Current Value: " + value); // Using `set()` to update the value atomicString.set("New Value"); System.out.println("Value after set(): " + atomicString.get()); // Using `lazySet()` to update the value atomicString.lazySet("Lazy Set Value"); System.out.println("Value after lazySet(): " + atomicString.get()); } }

La méthode compareAndSet(V expect, V update) met à jour la valeur si la valeur actuelle correspond à la valeur attendue. Elle retourne true si la mise à jour a été réussie, et false si la valeur actuelle ne correspondait pas à la valeur attendue. En revanche, la méthode getAndSet(V newValue) définit une nouvelle valeur et retourne la valeur précédente.

java

Main

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { public static void main(String[] args) { // Initialize an `AtomicReference` with an initial value AtomicReference<String> atomicString = new AtomicReference<>("Initial Value"); // Demonstrate `compareAndSet` boolean success = atomicString.compareAndSet("Initial Value", "Updated Value"); System.out.println("compareAndSet success (expected true): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); success = atomicString.compareAndSet("Wrong Value", "Another Update"); System.out.println("compareAndSet success (expected false): " + success); System.out.println("Current value after compareAndSet: " + atomicString.get()); // Demonstrate `getAndSet` String previousValue = atomicString.getAndSet("New Value with getAndSet"); System.out.println("Previous value from getAndSet: " + previousValue); System.out.println("Current value after getAndSet: " + atomicString.get()); } }

Les méthodes getAndIncrement() et getAndDecrement() incrémentent ou décrémentent la valeur actuelle de un et retournent la valeur précédente. Ces méthodes s'appliquent aux variables atomiques numériques telles que AtomicInteger et AtomicLong. En revanche, les méthodes incrementAndGet() et decrementAndGet() incrémentent ou décrémentent également la valeur actuelle de un mais retournent la nouvelle valeur.

java

Main

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with initial value AtomicInteger atomicInt = new AtomicInteger(10); // Demonstrate `getAndIncrement()` for `AtomicInteger` int oldValueInt = atomicInt.getAndIncrement(); System.out.println("Value before getAndIncrement(): " + oldValueInt); // Should print 10 System.out.println("Value after getAndIncrement(): " + atomicInt.get()); // Should print 11 // Demonstrate `getAndDecrement()` for `AtomicInteger` int oldValueIntDec = atomicInt.getAndDecrement(); System.out.println("Value before getAndDecrement(): " + oldValueIntDec); // Should print 11 System.out.println("Value after getAndDecrement(): " + atomicInt.get()); // Should print 10 // Demonstrate `incrementAndGet()` for `AtomicInteger` int newValueInt = atomicInt.incrementAndGet(); System.out.println("Value after incrementAndGet(): " + newValueInt); // Should print 11 System.out.println("Current value after incrementAndGet(): " + atomicInt.get()); // Should print 11 // Demonstrate `decrementAndGet()` for `AtomicInteger` int newValueIntDec = atomicInt.decrementAndGet(); System.out.println("Value after decrementAndGet(): " + newValueIntDec); // Should print 10 System.out.println("Current value after decrementAndGet(): " + atomicInt.get()); // Should print 10 } }

La méthode getAndAdd(int delta) ajoute la valeur spécifiée (delta) à la valeur actuelle et renvoie la valeur précédente. Cette méthode est utilisée avec des variables atomiques numériques. D'autre part, la méthode addAndGet(int delta) ajoute également la valeur spécifiée (delta) à la valeur actuelle mais renvoie la nouvelle valeur.

java

Main

copy
123456789101112131415161718192021222324252627282930
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { public static void main(String[] args) { // Initialize `AtomicInteger` with an initial value AtomicInteger atomicInt = new AtomicInteger(50); // Demonstrate `getAndAdd(int delta)` int previousValue = atomicInt.getAndAdd(10); System.out.println("Value before getAndAdd(10): " + previousValue); // Should print 50 System.out.println("Value after getAndAdd(10): " + atomicInt.get()); // Should print 60 // Demonstrate `getAndAdd()` with another delta int previousValue2 = atomicInt.getAndAdd(5); System.out.println("Value before getAndAdd(5): " + previousValue2); // Should print 60 System.out.println("Value after getAndAdd(5): " + atomicInt.get()); // Should print 65 // Demonstrate `addAndGet(int delta)` int newValue = atomicInt.addAndGet(20); System.out.println("Value after addAndGet(20): " + newValue); // Should print 85 System.out.println("Current value after addAndGet(20): " + atomicInt.get()); // Should print 85 // Demonstrate `addAndGet()` with another delta int newValue2 = atomicInt.addAndGet(-15); System.out.println("Value after addAndGet(-15): " + newValue2); // Should print 70 System.out.println("Current value after addAndGet(-15): " + atomicInt.get()); // Should print 70 } }

Exemples d'utilisation des variables atomiques

Exemple avec AtomicInteger (AtomicLong, AtomicBoolean sont les mêmes, donc il n'y aura pas d'exemples séparés pour eux).

Tâche : Implémenter un compteur qui est sûrement incrémenté par plusieurs threads.

java

Main

copy
1234567891011121314151617181920
package com.example; import java.util.concurrent.atomic.AtomicInteger; public class Main { private AtomicInteger counter = new AtomicInteger(0); public void increment() { int oldValue = counter.getAndIncrement(); System.out.println(Thread.currentThread().getName() + ": Counter was " + oldValue + ", now " + counter.get()); } public static void main(String[] args) { Main atomicCounter = new Main(); for (int i = 0; i < 5; i++) { new Thread(atomicCounter::increment).start(); } } }

Comme vous pouvez le voir, nous n'avons utilisé aucune synchronisation ici, car la variable atomique elle-même fournit cette fonction.

Cet exemple utilise AtomicInteger pour incrémenter en toute sécurité le compteur. La méthode getAndIncrement() renvoie d'abord la valeur actuelle de la variable, puis l'incrémente de un. Cela se produit atomiquement, garantissant que la variable est mise à jour correctement.

Exemple avec AtomicReference

Tâche : Fournir une mise à jour atomique d'une référence à un objet.

java

Main

copy
123456789101112131415161718192021222324
package com.example; import java.util.concurrent.atomic.AtomicReference; public class Main { // `AtomicReference` to safely update and access a shared `String` value private AtomicReference<String> sharedString = new AtomicReference<>("Initial"); public void updateValue(String newValue) { // Atomically sets the new value and gets the old value String oldValue = sharedString.getAndSet(newValue); // Prints the old and new values, along with the thread name System.out.println(Thread.currentThread().getName() + ": Value was " + oldValue + ", now " + sharedString.get()); } public static void main(String[] args) { Main example = new Main(); // Creates and starts 3 threads, each updating the shared value for (int i = 0; i < 3; i++) { new Thread(() -> example.updateValue("Updated by " + Thread.currentThread().getName())).start(); } } }

AtomicReference est utilisé ici pour mettre à jour atomiquement la valeur d'une référence de chaîne. La méthode getAndSet() définit atomiquement la nouvelle valeur et renvoie la valeur précédente.

Remarque

Contrairement aux variables normales, qui nécessitent une synchronisation supplémentaire, les variables atomiques utilisent des primitives de bas niveau pour minimiser les frais généraux et améliorer les performances. Cela les rend particulièrement bien adaptées aux systèmes à haute concurrence.

1. Quel est l'avantage d'utiliser des variables atomiques dans la programmation multithread ?

2. Quelle méthode de variable atomique fournit un changement de valeur atomique si la valeur actuelle est la même que la valeur attendue ?

3. Que garantit la méthode set() dans les variables atomiques ?

Quel est l'avantage d'utiliser des variables atomiques dans la programmation multithread ?

Quel est l'avantage d'utiliser des variables atomiques dans la programmation multithread ?

Sélectionnez la réponse correcte

Quelle méthode de variable atomique fournit un changement de valeur atomique si la valeur actuelle est la même que la valeur attendue ?

Quelle méthode de variable atomique fournit un changement de valeur atomique si la valeur actuelle est la même que la valeur attendue ?

Sélectionnez la réponse correcte

Que garantit la méthode set() dans les variables atomiques ?

Que garantit la méthode set() dans les variables atomiques ?

Sélectionnez la réponse correcte

Tout était clair ?

Comment pouvons-nous l'améliorer ?

Merci pour vos commentaires !

Section 3. Chapitre 5
We're sorry to hear that something went wrong. What happened?
some-alt