Atomära Variabler
Vi har redan behandlat vad atomitet är och vilka problem det kan orsaka i den första delen av denna kurs. Då hanterade vi problemet med hjälp av synchroniserade block eller metoder. Nu kommer vi att undersöka hur vi kan uppnå samma resultat på ett enklare sätt genom att använda en atomär klass.
Vad är atomära variabler?
Atomära variabler säkerställer att operationer (läsning, skrivning, inkrementering) på variabler utförs atomärt, vilket innebär att de exekveras sammanhängande och säkert i en flertrådad miljö. Detta garanterar att operationen blir fullständigt genomförd, utan risk för störning från andra trådar under dess exekvering.
Varför behöver vi atomära variabler?
Utan användning av atomära variabler eller andra synkroniseringsmekanismer kan operationer som inkrementering (++) vara osäkra. Till exempel, när flera trådar samtidigt får tillgång till samma variabel kan uppdateringar gå förlorade, vilket leder till felaktiga resultat. Atomära variabler löser detta problem genom att säkerställa att operationer på dem utförs sekventiellt.
Vi diskuterade tidigare detta problem när inkrementeringsoperationen delades upp i tre steg (läs, öka, skriv), men med atomära variabler sker allt i en operation!
Typer av atomära variabler i Java
Generellt finns det många atomära implementationer, och vi kommer inte att täcka alla här, eftersom det skulle ta för lång tid.
Java tillhandahåller flera atomära variabelklasser i paketet java.util.concurrent.atomic, var och en utformad för att hantera en specifik datatyp:
AtomicInteger: för atomära operationer på int;AtomicLong: för atomära operationer på long;AtomicBoolean: för atomära operationer på boolean;AtomicReference<V>: för atomära operationer på objekt (generisk typ).
Metoder
Metoden get() returnerar aktuellt värde för en variabel. Metoden set(V newValue) anger ett nytt värde för variabeln. Å andra sidan liknar lazySet(V newValue) set(), men kan fördröja uppdateringen av värdet och erbjuder en ordnad uppdatering i vissa situationer.
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()); } }
Metoden compareAndSet(V expect, V update) uppdaterar värdet om det aktuella värdet motsvarar det förväntade värdet. Den returnerar true om uppdateringen var lyckad, och false om det aktuella värdet inte motsvarade det förväntade värdet. I kontrast sätter metoden getAndSet(V newValue) ett nytt värde och returnerar det föregående värdet.
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()); } }
getAndIncrement() och getAndDecrement() metoderna ökar eller minskar det aktuella värdet med ett och returnerar det föregående värdet. Dessa metoder används för numeriska atomära variabler såsom AtomicInteger och AtomicLong. Till skillnad från dessa ökar eller minskar incrementAndGet() och decrementAndGet() metoderna också det aktuella värdet med ett men returnerar det nya värdet.
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 } }
getAndAdd(int delta) metoden lägger till det angivna värdet (delta) till det aktuella värdet och returnerar det föregående värdet. Denna metod används med numeriska atomära variabler. Å andra sidan lägger addAndGet(int delta) metoden också till det angivna värdet (delta) till det aktuella värdet men returnerar det nya värdet.
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 } }
Exempel på användning av atomära variabler
Exempel med AtomicInteger (AtomicLong, AtomicBoolean fungerar på samma sätt, så det ges inga separata exempel för dem).
Uppgift: Implementering av en räknare som säkert ökas av flera trådar.
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(); } } }
Som du kan se använde vi ingen synkronisering här, eftersom atomvariabeln själv tillhandahåller denna funktion.
Detta exempel använder AtomicInteger för att säkert inkrementera räknaren. Metoden getAndIncrement() returnerar först det aktuella värdet av variabeln och ökar sedan värdet med ett. Detta sker atomärt, vilket säkerställer att variabeln uppdateras korrekt.
Exempel med AtomicReference
Uppgift: Tillhandahålla atomär uppdatering av en referens till ett objekt.
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 används här för att atomärt uppdatera värdet av en strängreferens. Metoden getAndSet() sätter atomärt det nya värdet och returnerar det tidigare värdet.
Till skillnad från vanliga variabler, som kräver ytterligare synkronisering, använder atomära variabler lågnivåprimitiver för att minimera overhead och förbättra prestanda. Detta gör dem särskilt lämpade för system med hög samtidighet.
1. Vilken är fördelen med att använda atomära variabler i flertrådade program?
2. Vilken metod för atomära variabler möjliggör en atomär värdeändring om det aktuella värdet är detsamma som det förväntade värdet?
3. Vad garanterar metoden set() för atomära variabler?
Tack för dina kommentarer!
Fråga AI
Fråga AI
Fråga vad du vill eller prova någon av de föreslagna frågorna för att starta vårt samtal
Awesome!
Completion rate improved to 3.33
Atomära Variabler
Svep för att visa menyn
Vi har redan behandlat vad atomitet är och vilka problem det kan orsaka i den första delen av denna kurs. Då hanterade vi problemet med hjälp av synchroniserade block eller metoder. Nu kommer vi att undersöka hur vi kan uppnå samma resultat på ett enklare sätt genom att använda en atomär klass.
Vad är atomära variabler?
Atomära variabler säkerställer att operationer (läsning, skrivning, inkrementering) på variabler utförs atomärt, vilket innebär att de exekveras sammanhängande och säkert i en flertrådad miljö. Detta garanterar att operationen blir fullständigt genomförd, utan risk för störning från andra trådar under dess exekvering.
Varför behöver vi atomära variabler?
Utan användning av atomära variabler eller andra synkroniseringsmekanismer kan operationer som inkrementering (++) vara osäkra. Till exempel, när flera trådar samtidigt får tillgång till samma variabel kan uppdateringar gå förlorade, vilket leder till felaktiga resultat. Atomära variabler löser detta problem genom att säkerställa att operationer på dem utförs sekventiellt.
Vi diskuterade tidigare detta problem när inkrementeringsoperationen delades upp i tre steg (läs, öka, skriv), men med atomära variabler sker allt i en operation!
Typer av atomära variabler i Java
Generellt finns det många atomära implementationer, och vi kommer inte att täcka alla här, eftersom det skulle ta för lång tid.
Java tillhandahåller flera atomära variabelklasser i paketet java.util.concurrent.atomic, var och en utformad för att hantera en specifik datatyp:
AtomicInteger: för atomära operationer på int;AtomicLong: för atomära operationer på long;AtomicBoolean: för atomära operationer på boolean;AtomicReference<V>: för atomära operationer på objekt (generisk typ).
Metoder
Metoden get() returnerar aktuellt värde för en variabel. Metoden set(V newValue) anger ett nytt värde för variabeln. Å andra sidan liknar lazySet(V newValue) set(), men kan fördröja uppdateringen av värdet och erbjuder en ordnad uppdatering i vissa situationer.
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()); } }
Metoden compareAndSet(V expect, V update) uppdaterar värdet om det aktuella värdet motsvarar det förväntade värdet. Den returnerar true om uppdateringen var lyckad, och false om det aktuella värdet inte motsvarade det förväntade värdet. I kontrast sätter metoden getAndSet(V newValue) ett nytt värde och returnerar det föregående värdet.
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()); } }
getAndIncrement() och getAndDecrement() metoderna ökar eller minskar det aktuella värdet med ett och returnerar det föregående värdet. Dessa metoder används för numeriska atomära variabler såsom AtomicInteger och AtomicLong. Till skillnad från dessa ökar eller minskar incrementAndGet() och decrementAndGet() metoderna också det aktuella värdet med ett men returnerar det nya värdet.
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 } }
getAndAdd(int delta) metoden lägger till det angivna värdet (delta) till det aktuella värdet och returnerar det föregående värdet. Denna metod används med numeriska atomära variabler. Å andra sidan lägger addAndGet(int delta) metoden också till det angivna värdet (delta) till det aktuella värdet men returnerar det nya värdet.
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 } }
Exempel på användning av atomära variabler
Exempel med AtomicInteger (AtomicLong, AtomicBoolean fungerar på samma sätt, så det ges inga separata exempel för dem).
Uppgift: Implementering av en räknare som säkert ökas av flera trådar.
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(); } } }
Som du kan se använde vi ingen synkronisering här, eftersom atomvariabeln själv tillhandahåller denna funktion.
Detta exempel använder AtomicInteger för att säkert inkrementera räknaren. Metoden getAndIncrement() returnerar först det aktuella värdet av variabeln och ökar sedan värdet med ett. Detta sker atomärt, vilket säkerställer att variabeln uppdateras korrekt.
Exempel med AtomicReference
Uppgift: Tillhandahålla atomär uppdatering av en referens till ett objekt.
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 används här för att atomärt uppdatera värdet av en strängreferens. Metoden getAndSet() sätter atomärt det nya värdet och returnerar det tidigare värdet.
Till skillnad från vanliga variabler, som kräver ytterligare synkronisering, använder atomära variabler lågnivåprimitiver för att minimera overhead och förbättra prestanda. Detta gör dem särskilt lämpade för system med hög samtidighet.
1. Vilken är fördelen med att använda atomära variabler i flertrådade program?
2. Vilken metod för atomära variabler möjliggör en atomär värdeändring om det aktuella värdet är detsamma som det förväntade värdet?
3. Vad garanterar metoden set() för atomära variabler?
Tack för dina kommentarer!