Kursinhalt
Multithreading in Java
Multithreading in Java
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 angegangen. Jetzt werden wir erkunden, wie man dasselbe Ergebnis einfacher mit einer atomaren Klasse erreichen kann.
Was sind atomare Variablen?
Atomare Variablen stellen sicher, dass Operationen (lesen, schreiben, inkrementieren) an Variablen atomar ausgeführt werden, was bedeutet, dass sie zusammenhängend und sicher in einer multithreaded Umgebung ausgeführt werden. Dies garantiert, dass die Operation vollständig abgeschlossen wird, ohne die Möglichkeit einer Beeinträchtigung durch andere Threads während ihrer Ausführung.
Warum brauchen wir atomare Variablen?
Ohne die Verwendung von atomaren Variablen oder anderen Synchronisationsmechanismen können Operationen wie Inkrement (++)
unsicher sein. Beispielsweise können bei gleichzeitigen Zugriffen mehrerer Threads auf dieselbe Variable 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.
Wir haben dieses Problem bereits besprochen, als die Inkrement-Operation in drei Schritte (lesen, inkrementieren, schreiben) unterteilt wurde, aber mit atomaren Variablen wird alles in einem Schritt erledigt!
Arten von atomaren Variablen in Java
Hinweis
Im Allgemeinen gibt es viele atomare Implementierungen, und wir werden hier nicht alle behandeln, da es zu lange dauern würde.
Java bietet mehrere atomare Variablen-Klassen im Paket java.util.concurrent.atomic
, die jeweils für einen spezifischen 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 get()
Methode gibt den aktuellen Wert einer Variablen zurück. Die set(V newValue)
Methode setzt einen neuen Wert für die Variable. Andererseits ist lazySet(V newValue)
ähnlich wie set()
, kann jedoch das Aktualisieren des Wertes verzögern, um in bestimmten Situationen ein geordnetes Update zu bieten.
Main
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()); } }
Die compareAndSet(V expect, V update)
Methode 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 mit dem erwarteten Wert übereinstimmte. Im Gegensatz dazu setzt die getAndSet(V newValue)
Methode einen neuen Wert und gibt den vorherigen Wert zurück.
Main
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()); } }
Die getAndIncrement()
und getAndDecrement()
Methoden erhöhen oder 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 incrementAndGet()
und decrementAndGet()
Methoden ebenfalls den aktuellen Wert um eins, geben jedoch den neuen Wert zurück.
Main
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 } }
Die getAndAdd(int delta)
Methode addiert den angegebenen Wert (delta) zum aktuellen Wert und gibt den vorherigen Wert zurück. Diese Methode wird mit numerischen atomaren Variablen verwendet. Andererseits addiert die addAndGet(int delta)
Methode ebenfalls den angegebenen Wert (delta) zum aktuellen Wert, gibt aber den neuen Wert zurück.
Main
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 } }
Beispiele für die Verwendung von Atomaren Variablen
Beispiel mit AtomicInteger
(AtomicLong
, AtomicBoolean
sind gleich, daher wird es keine separaten Beispiele für sie geben).
Aufgabe: Einen Zähler zu implementieren, der von mehreren Threads sicher inkrementiert wird.
Main
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(); } } }
Wie Sie sehen, haben wir hier keine Synchronisation verwendet, da die atomare Variable diese Funktion selbst bereitstellt.
Dieses Beispiel verwendet AtomicInteger
, um den Zähler sicher zu inkrementieren. Die Methode getAndIncrement()
gibt zuerst den aktuellen Wert der Variable zurück und erhöht ihn dann um eins. Dies geschieht atomar, wodurch sichergestellt wird, dass die Variable korrekt aktualisiert wird.
Beispiel mit AtomicReference
Aufgabe: Bieten Sie eine atomare Aktualisierung eines Verweises auf ein Objekt an.
Main
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
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.
Hinweis
Im Gegensatz zu regulären Variablen, die zusätzliche *Synchronisation erfordern, nutzen atomare Variablen Low-Level-Primitiven, um den Overhead zu minimieren und die Leistung zu verbessern. Dies macht sie besonders geeignet für Hochkonkurrenzsysteme.
1. Was ist der Vorteil der Verwendung von atomaren Variablen in der Multithread-Programmierung?
2. Welche Methode der atomaren Variablen bietet eine atomare Wertänderung, wenn der aktuelle Wert dem erwarteten Wert entspricht?
3. Was garantiert die set()-Methode bei atomaren Variablen?
Danke für Ihr Feedback!