Atomare Variablen
Wir haben bereits behandelt, was Atomizität ist und welche Probleme sie verursachen kann, im ersten Abschnitt dieses Kurses. Damals haben wir das Problem mit synchronisierten Blöcken oder Methoden gelöst. Jetzt werden wir untersuchen, wie sich das gleiche Ergebnis einfacher durch die Verwendung einer atomaren Klasse erreichen lässt.
Was sind atomare Variablen?
Atomare Variablen stellen sicher, dass Operationen (Lesen, Schreiben, Inkrementieren) an Variablen atomar ausgeführt werden, das heißt, sie werden zusammenhängend und sicher in einer multithreaded Umgebung durchgeführt. Dies garantiert, dass die Operation vollständig abgeschlossen wird, ohne dass andere Threads während der Ausführung eingreifen können.
Warum benötigen wir atomare Variablen?
Ohne den Einsatz von atomaren Variablen oder anderen Synchronisationsmechanismen können Operationen wie Inkrementierung (++) unsicher sein. Wenn beispielsweise mehrere Threads gleichzeitig auf dieselbe Variable zugreifen, können Aktualisierungen verloren gehen, was zu falschen Ergebnissen führt. Atomare Variablen lösen dieses Problem, indem sie sicherstellen, dass Operationen an ihnen sequenziell ausgeführt werden.
Dieses Problem wurde bereits zuvor besprochen, als die Inkrementierungsoperation in drei Schritte (lesen, inkrementieren, schreiben) unterteilt wurde. Mit atomaren Variablen erfolgt dies jedoch in einem einzigen Schritt!
Typen von atomaren Variablen in Java
Im Allgemeinen gibt es viele atomare Implementierungen, und wir werden hier nicht alle behandeln, da dies zu viel Zeit in Anspruch nehmen würde.
Java stellt mehrere atomare Variablenklassen im Paket java.util.concurrent.atomic bereit, die jeweils für einen bestimmten Datentyp ausgelegt sind:
AtomicInteger: für atomare Operationen auf int;AtomicLong: für atomare Operationen auf long;AtomicBoolean: für atomare Operationen auf boolean;AtomicReference<V>: für atomare Operationen auf Objekten (generischer Typ).
Methoden
Die Methode get() gibt den aktuellen Wert einer Variablen zurück. Die Methode set(V newValue) setzt einen neuen Wert für die Variable. Im Gegensatz dazu ist lazySet(V newValue) ähnlich wie set(), kann aber das Aktualisieren des Wertes verzögern und bietet in bestimmten Situationen eine geordnete Aktualisierung.
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()); } }
Die Methode compareAndSet(V expect, V update) aktualisiert den Wert, wenn der aktuelle Wert dem erwarteten Wert entspricht. Sie gibt true zurück, wenn die Aktualisierung erfolgreich war, und false, wenn der aktuelle Wert nicht dem erwarteten Wert entsprach. Im Gegensatz dazu setzt die Methode getAndSet(V newValue) einen neuen Wert und gibt den vorherigen Wert zurück.
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()); } }
Die Methoden getAndIncrement() und getAndDecrement() erhöhen bzw. verringern den aktuellen Wert um eins und geben den vorherigen Wert zurück. Diese Methoden gelten für numerische atomare Variablen wie AtomicInteger und AtomicLong. Im Gegensatz dazu erhöhen oder verringern die Methoden incrementAndGet() und decrementAndGet() ebenfalls den aktuellen Wert um eins, geben jedoch den neuen Wert zurück.
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 } }
Die Methode getAndAdd(int delta) addiert den angegebenen Wert (delta) zum aktuellen Wert und gibt den vorherigen Wert zurück. Diese Methode wird mit numerischen atomaren Variablen verwendet. Im Gegensatz dazu addiert die Methode addAndGet(int delta) ebenfalls den angegebenen Wert (delta) zum aktuellen Wert, gibt jedoch den neuen Wert zurück.
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 } }
Beispiele für die Verwendung von atomaren Variablen
Beispiel mit AtomicInteger (AtomicLong, AtomicBoolean funktionieren identisch, daher werden keine separaten Beispiele für diese gezeigt).
Aufgabe: Implementierung eines Zählers, der von mehreren Threads sicher inkrementiert wird.
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(); } } }
Wie Sie sehen, wurde hier keine Synchronisierung verwendet, da die atomare Variable diese Funktion selbst bereitstellt.
Dieses Beispiel verwendet AtomicInteger, um den Zähler sicher zu inkrementieren. Die Methode getAndIncrement() gibt zunächst den aktuellen Wert der Variablen zurück und erhöht ihn dann um eins. Dies geschieht atomar und stellt sicher, dass die Variable korrekt aktualisiert wird.
Beispiel mit AtomicReference
Aufgabe: Atomare Aktualisierung einer Referenz auf ein Objekt bereitstellen.
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 wird hier verwendet, um den Wert einer String-Referenz atomar zu aktualisieren. Die Methode getAndSet() setzt atomar den neuen Wert und gibt den vorherigen Wert zurück.
Im Gegensatz zu regulären Variablen, die zusätzliche Synchronisation erfordern, nutzen atomare Variablen niedrigstufige Primitiven, um den Overhead zu minimieren und die Leistung zu steigern. Dadurch sind sie besonders gut für Systeme mit hoher Parallelität geeignet.
1. Was ist der Vorteil der Verwendung von atomaren Variablen in der nebenläufigen Programmierung?
2. Welche Methode einer atomaren Variablen ermöglicht eine atomare Wertänderung, wenn der aktuelle Wert dem erwarteten Wert entspricht?
3. Was garantiert die Methode set() bei atomaren Variablen?
Danke für Ihr Feedback!
Fragen Sie AI
Fragen Sie AI
Fragen Sie alles oder probieren Sie eine der vorgeschlagenen Fragen, um unser Gespräch zu beginnen
Awesome!
Completion rate improved to 3.33
Atomare Variablen
Swipe um das Menü anzuzeigen
Wir haben bereits behandelt, was Atomizität ist und welche Probleme sie verursachen kann, im ersten Abschnitt dieses Kurses. Damals haben wir das Problem mit synchronisierten Blöcken oder Methoden gelöst. Jetzt werden wir untersuchen, wie sich das gleiche Ergebnis einfacher durch die Verwendung einer atomaren Klasse erreichen lässt.
Was sind atomare Variablen?
Atomare Variablen stellen sicher, dass Operationen (Lesen, Schreiben, Inkrementieren) an Variablen atomar ausgeführt werden, das heißt, sie werden zusammenhängend und sicher in einer multithreaded Umgebung durchgeführt. Dies garantiert, dass die Operation vollständig abgeschlossen wird, ohne dass andere Threads während der Ausführung eingreifen können.
Warum benötigen wir atomare Variablen?
Ohne den Einsatz von atomaren Variablen oder anderen Synchronisationsmechanismen können Operationen wie Inkrementierung (++) unsicher sein. Wenn beispielsweise mehrere Threads gleichzeitig auf dieselbe Variable zugreifen, können Aktualisierungen verloren gehen, was zu falschen Ergebnissen führt. Atomare Variablen lösen dieses Problem, indem sie sicherstellen, dass Operationen an ihnen sequenziell ausgeführt werden.
Dieses Problem wurde bereits zuvor besprochen, als die Inkrementierungsoperation in drei Schritte (lesen, inkrementieren, schreiben) unterteilt wurde. Mit atomaren Variablen erfolgt dies jedoch in einem einzigen Schritt!
Typen von atomaren Variablen in Java
Im Allgemeinen gibt es viele atomare Implementierungen, und wir werden hier nicht alle behandeln, da dies zu viel Zeit in Anspruch nehmen würde.
Java stellt mehrere atomare Variablenklassen im Paket java.util.concurrent.atomic bereit, die jeweils für einen bestimmten Datentyp ausgelegt sind:
AtomicInteger: für atomare Operationen auf int;AtomicLong: für atomare Operationen auf long;AtomicBoolean: für atomare Operationen auf boolean;AtomicReference<V>: für atomare Operationen auf Objekten (generischer Typ).
Methoden
Die Methode get() gibt den aktuellen Wert einer Variablen zurück. Die Methode set(V newValue) setzt einen neuen Wert für die Variable. Im Gegensatz dazu ist lazySet(V newValue) ähnlich wie set(), kann aber das Aktualisieren des Wertes verzögern und bietet in bestimmten Situationen eine geordnete Aktualisierung.
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()); } }
Die Methode compareAndSet(V expect, V update) aktualisiert den Wert, wenn der aktuelle Wert dem erwarteten Wert entspricht. Sie gibt true zurück, wenn die Aktualisierung erfolgreich war, und false, wenn der aktuelle Wert nicht dem erwarteten Wert entsprach. Im Gegensatz dazu setzt die Methode getAndSet(V newValue) einen neuen Wert und gibt den vorherigen Wert zurück.
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()); } }
Die Methoden getAndIncrement() und getAndDecrement() erhöhen bzw. verringern den aktuellen Wert um eins und geben den vorherigen Wert zurück. Diese Methoden gelten für numerische atomare Variablen wie AtomicInteger und AtomicLong. Im Gegensatz dazu erhöhen oder verringern die Methoden incrementAndGet() und decrementAndGet() ebenfalls den aktuellen Wert um eins, geben jedoch den neuen Wert zurück.
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 } }
Die Methode getAndAdd(int delta) addiert den angegebenen Wert (delta) zum aktuellen Wert und gibt den vorherigen Wert zurück. Diese Methode wird mit numerischen atomaren Variablen verwendet. Im Gegensatz dazu addiert die Methode addAndGet(int delta) ebenfalls den angegebenen Wert (delta) zum aktuellen Wert, gibt jedoch den neuen Wert zurück.
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 } }
Beispiele für die Verwendung von atomaren Variablen
Beispiel mit AtomicInteger (AtomicLong, AtomicBoolean funktionieren identisch, daher werden keine separaten Beispiele für diese gezeigt).
Aufgabe: Implementierung eines Zählers, der von mehreren Threads sicher inkrementiert wird.
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(); } } }
Wie Sie sehen, wurde hier keine Synchronisierung verwendet, da die atomare Variable diese Funktion selbst bereitstellt.
Dieses Beispiel verwendet AtomicInteger, um den Zähler sicher zu inkrementieren. Die Methode getAndIncrement() gibt zunächst den aktuellen Wert der Variablen zurück und erhöht ihn dann um eins. Dies geschieht atomar und stellt sicher, dass die Variable korrekt aktualisiert wird.
Beispiel mit AtomicReference
Aufgabe: Atomare Aktualisierung einer Referenz auf ein Objekt bereitstellen.
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 wird hier verwendet, um den Wert einer String-Referenz atomar zu aktualisieren. Die Methode getAndSet() setzt atomar den neuen Wert und gibt den vorherigen Wert zurück.
Im Gegensatz zu regulären Variablen, die zusätzliche Synchronisation erfordern, nutzen atomare Variablen niedrigstufige Primitiven, um den Overhead zu minimieren und die Leistung zu steigern. Dadurch sind sie besonders gut für Systeme mit hoher Parallelität geeignet.
1. Was ist der Vorteil der Verwendung von atomaren Variablen in der nebenläufigen Programmierung?
2. Welche Methode einer atomaren Variablen ermöglicht eine atomare Wertänderung, wenn der aktuelle Wert dem erwarteten Wert entspricht?
3. Was garantiert die Methode set() bei atomaren Variablen?
Danke für Ihr Feedback!