Variables Atomiques
Nous avons déjà abordé ce qu'est l'atomicité et les problèmes qu'elle peut engendrer dans la première section de ce cours. À ce moment-là, nous avons traité la question à l'aide de blocs synchronisés ou de méthodes synchronisées. À présent, nous allons examiner comment obtenir le même résultat plus simplement en utilisant une classe atomique.
Que sont les variables atomiques ?
Les variables atomiques garantissent que les opérations (lecture, écriture, incrémentation) sur les variables sont effectuées de manière atomique, c'est-à-dire qu'elles sont exécutées de façon contiguë et en toute sécurité dans un environnement multithread. Cela assure que l'opération sera entièrement réalisée, sans risque 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 telles que l'incrémentation (++) peuvent être non sûres. Par exemple, lorsque plusieurs threads accèdent simultanément à la même variable, des 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 celles-ci sont exécutées séquentiellement.
Nous avons déjà abordé 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 effectué en une seule opération !
Types de variables atomiques en Java
En général, il existe de nombreuses implémentations atomiques, et nous ne les aborderons pas toutes ici, car cela prendrait trop de temps.
Java propose 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. En revanche, 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.
Main.java
123456789101112131415161718192021package 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é effectuée avec succès, 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.
Main.java
123456789101112131415161718192021222324package 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 renvoient 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 renvoient la nouvelle valeur.
Main.java
123456789101112131415161718192021222324252627282930package 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 les variables atomiques numériques. En revanche, la méthode addAndGet(int delta) ajoute également la valeur spécifiée (delta) à la valeur actuelle mais renvoie la nouvelle valeur.
Main.java
123456789101112131415161718192021222324252627282930package 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 fonctionnent de la même manière, il n'y aura donc pas d'exemples séparés pour eux).
Objectif : Implémentation d'un compteur incrémenté en toute sécurité par plusieurs threads.
Main.java
1234567891011121314151617181920package 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 constater, aucune synchronisation n'a été utilisée ici, car la variable atomique assure elle-même cette fonction.
Cet exemple utilise AtomicInteger pour incrémenter le compteur en toute sécurité. La méthode getAndIncrement() retourne d'abord la valeur actuelle de la variable, puis l'incrémente de un. Cette opération se produit de manière atomique, garantissant que la variable est mise à jour correctement.
Exemple avec AtomicReference
Objectif : Fournir une mise à jour atomique d'une référence vers un objet.
Main.java
123456789101112131415161718192021222324package 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 de manière atomique la valeur d'une référence de chaîne. La méthode getAndSet() définit atomiquement la nouvelle valeur et retourne la valeur précédente.
Contrairement aux variables classiques, qui nécessitent une synchronisation supplémentaire, les variables atomiques utilisent des primitives de bas niveau afin de minimiser la surcharge et d'améliorer les performances. Cela les rend particulièrement adaptées aux systèmes à forte concurrence.
1. Quel est l'avantage d'utiliser des variables atomiques en programmation multithreadée ?
2. Quelle méthode de variable atomique permet une modification atomique de la valeur si la valeur actuelle est identique à la valeur attendue ?
3. Que garantit la méthode set() pour les variables atomiques ?
Merci pour vos commentaires !
Demandez à l'IA
Demandez à l'IA
Posez n'importe quelle question ou essayez l'une des questions suggérées pour commencer notre discussion
Can you explain how atomic variables differ from using synchronized blocks?
What are some real-world scenarios where atomic variables are especially useful?
Could you provide a simple code example using AtomicInteger?
Awesome!
Completion rate improved to 3.33
Variables Atomiques
Glissez pour afficher le menu
Nous avons déjà abordé ce qu'est l'atomicité et les problèmes qu'elle peut engendrer dans la première section de ce cours. À ce moment-là, nous avons traité la question à l'aide de blocs synchronisés ou de méthodes synchronisées. À présent, nous allons examiner comment obtenir le même résultat plus simplement en utilisant une classe atomique.
Que sont les variables atomiques ?
Les variables atomiques garantissent que les opérations (lecture, écriture, incrémentation) sur les variables sont effectuées de manière atomique, c'est-à-dire qu'elles sont exécutées de façon contiguë et en toute sécurité dans un environnement multithread. Cela assure que l'opération sera entièrement réalisée, sans risque 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 telles que l'incrémentation (++) peuvent être non sûres. Par exemple, lorsque plusieurs threads accèdent simultanément à la même variable, des 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 celles-ci sont exécutées séquentiellement.
Nous avons déjà abordé 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 effectué en une seule opération !
Types de variables atomiques en Java
En général, il existe de nombreuses implémentations atomiques, et nous ne les aborderons pas toutes ici, car cela prendrait trop de temps.
Java propose 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. En revanche, 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.
Main.java
123456789101112131415161718192021package 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é effectuée avec succès, 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.
Main.java
123456789101112131415161718192021222324package 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 renvoient 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 renvoient la nouvelle valeur.
Main.java
123456789101112131415161718192021222324252627282930package 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 les variables atomiques numériques. En revanche, la méthode addAndGet(int delta) ajoute également la valeur spécifiée (delta) à la valeur actuelle mais renvoie la nouvelle valeur.
Main.java
123456789101112131415161718192021222324252627282930package 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 fonctionnent de la même manière, il n'y aura donc pas d'exemples séparés pour eux).
Objectif : Implémentation d'un compteur incrémenté en toute sécurité par plusieurs threads.
Main.java
1234567891011121314151617181920package 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 constater, aucune synchronisation n'a été utilisée ici, car la variable atomique assure elle-même cette fonction.
Cet exemple utilise AtomicInteger pour incrémenter le compteur en toute sécurité. La méthode getAndIncrement() retourne d'abord la valeur actuelle de la variable, puis l'incrémente de un. Cette opération se produit de manière atomique, garantissant que la variable est mise à jour correctement.
Exemple avec AtomicReference
Objectif : Fournir une mise à jour atomique d'une référence vers un objet.
Main.java
123456789101112131415161718192021222324package 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 de manière atomique la valeur d'une référence de chaîne. La méthode getAndSet() définit atomiquement la nouvelle valeur et retourne la valeur précédente.
Contrairement aux variables classiques, qui nécessitent une synchronisation supplémentaire, les variables atomiques utilisent des primitives de bas niveau afin de minimiser la surcharge et d'améliorer les performances. Cela les rend particulièrement adaptées aux systèmes à forte concurrence.
1. Quel est l'avantage d'utiliser des variables atomiques en programmation multithreadée ?
2. Quelle méthode de variable atomique permet une modification atomique de la valeur si la valeur actuelle est identique à la valeur attendue ?
3. Que garantit la méthode set() pour les variables atomiques ?
Merci pour vos commentaires !