Atomiske Variabler
Vi har allerede dekket hva atomisitet er og hvilke problemer det kan forårsake i den første delen av dette kurset. Den gangen løste vi problemet ved å bruke synchroniserte blokker eller metoder. Nå skal vi se på hvordan vi kan oppnå samme resultat enklere ved å bruke en atomisk klasse.
Hva er atomiske variabler?
Atomiske variabler sikrer at operasjoner (lesing, skriving, inkrementering) på variabler utføres atomisk, noe som betyr at de utføres kontinuerlig og trygt i et multitrådet miljø. Dette garanterer at operasjonen blir fullført i sin helhet, uten mulighet for innblanding fra andre tråder under utførelsen.
Hvorfor trenger vi atomiske variabler?
Uten bruk av atomiske variabler eller andre synkroniseringsmekanismer kan operasjoner som inkrement (++) være usikre. For eksempel, når flere tråder får tilgang til samme variabel samtidig, kan oppdateringer gå tapt, noe som fører til feilaktige resultater. Atomiske variabler løser dette problemet ved å sikre at operasjoner på dem utføres sekvensielt.
Vi har tidligere diskutert dette problemet da inkrement-operasjonen ble delt opp i tre steg (les, øk, skriv), men med atomiske variabler gjøres alt i én operasjon!
Typer av atomiske variabler i Java
Generelt finnes det mange atomiske implementasjoner, og vi vil ikke dekke alle her, da det ville tatt for lang tid.
Java tilbyr flere atomiske variabelklasser i java.util.concurrent.atomic-pakken, hver designet for å håndtere en spesifikk datatyp:
AtomicInteger: for atomiske operasjoner på int;AtomicLong: for atomiske operasjoner på long;AtomicBoolean: for atomiske operasjoner på boolean;AtomicReference<V>: for atomiske operasjoner på objekter (generisk type).
Metoder
get()-metoden returnerer nåværende verdi av en variabel. set(V newValue)-metoden setter en ny verdi for variabelen. På den annen side er lazySet(V newValue) lik set(), men kan utsette oppdateringen av verdien, og gir en ordnet oppdatering i visse situasjoner.
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) oppdaterer verdien hvis nåværende verdi samsvarer med forventet verdi. Den returnerer true hvis oppdateringen var vellykket, og false hvis nåværende verdi ikke samsvarte med forventet verdi. Til sammenligning setter metoden getAndSet(V newValue) en ny verdi og returnerer forrige verdi.
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()); } }
Metodene getAndIncrement() og getAndDecrement() øker eller reduserer den nåværende verdien med én og returnerer den forrige verdien. Disse metodene gjelder for numeriske atomiske variabler som AtomicInteger og AtomicLong. I motsetning til dette, øker eller reduserer metodene incrementAndGet() og decrementAndGet() også den nåværende verdien med én, men returnerer den nye verdien.
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 } }
Metoden getAndAdd(int delta) legger til den angitte verdien (delta) til den nåværende verdien og returnerer den forrige verdien. Denne metoden brukes med numeriske atomiske variabler. På den annen side legger metoden addAndGet(int delta) også til den angitte verdien (delta) til den nåværende verdien, men returnerer den nye verdien.
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 } }
Eksempler på bruk av atomiske variabler
Eksempel med AtomicInteger (AtomicLong, AtomicBoolean fungerer på samme måte, så det gis ikke egne eksempler for dem).
Oppgave: Implementere en teller som trygt økes av flere tråder.
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 ser brukte vi ikke noen synkronisering her, ettersom atomvariabelen selv gir denne funksjonen.
Dette eksempelet bruker AtomicInteger for å trygt øke telleren. Metoden getAndIncrement() returnerer først den nåværende verdien av variabelen, og øker deretter verdien med én. Dette skjer atomisk, noe som sikrer at variabelen oppdateres korrekt.
Eksempel med AtomicReference
Oppgave: Gi atomisk oppdatering av en referanse til et 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 brukes her for å oppdatere verdien til en strengreferanse atomisk. Metoden getAndSet() setter den nye verdien atomisk og returnerer den forrige verdien.
I motsetning til vanlige variabler, som krever ekstra synkronisering, benytter atomiske variabler lavnivå-primitiver for å minimere overhead og forbedre ytelsen. Dette gjør dem spesielt godt egnet for systemer med høy grad av samtidighet.
1. Hva er fordelen med å bruke atomiske variabler i flertrådede programmer?
2. Hvilken metode for atomiske variabler gir en atomisk endring av verdien hvis den nåværende verdien er lik forventet verdi?
3. Hva garanterer set()-metoden for atomiske variabler?
Takk for tilbakemeldingene dine!
Spør AI
Spør AI
Spør om hva du vil, eller prøv ett av de foreslåtte spørsmålene for å starte chatten vår
Awesome!
Completion rate improved to 3.33
Atomiske Variabler
Sveip for å vise menyen
Vi har allerede dekket hva atomisitet er og hvilke problemer det kan forårsake i den første delen av dette kurset. Den gangen løste vi problemet ved å bruke synchroniserte blokker eller metoder. Nå skal vi se på hvordan vi kan oppnå samme resultat enklere ved å bruke en atomisk klasse.
Hva er atomiske variabler?
Atomiske variabler sikrer at operasjoner (lesing, skriving, inkrementering) på variabler utføres atomisk, noe som betyr at de utføres kontinuerlig og trygt i et multitrådet miljø. Dette garanterer at operasjonen blir fullført i sin helhet, uten mulighet for innblanding fra andre tråder under utførelsen.
Hvorfor trenger vi atomiske variabler?
Uten bruk av atomiske variabler eller andre synkroniseringsmekanismer kan operasjoner som inkrement (++) være usikre. For eksempel, når flere tråder får tilgang til samme variabel samtidig, kan oppdateringer gå tapt, noe som fører til feilaktige resultater. Atomiske variabler løser dette problemet ved å sikre at operasjoner på dem utføres sekvensielt.
Vi har tidligere diskutert dette problemet da inkrement-operasjonen ble delt opp i tre steg (les, øk, skriv), men med atomiske variabler gjøres alt i én operasjon!
Typer av atomiske variabler i Java
Generelt finnes det mange atomiske implementasjoner, og vi vil ikke dekke alle her, da det ville tatt for lang tid.
Java tilbyr flere atomiske variabelklasser i java.util.concurrent.atomic-pakken, hver designet for å håndtere en spesifikk datatyp:
AtomicInteger: for atomiske operasjoner på int;AtomicLong: for atomiske operasjoner på long;AtomicBoolean: for atomiske operasjoner på boolean;AtomicReference<V>: for atomiske operasjoner på objekter (generisk type).
Metoder
get()-metoden returnerer nåværende verdi av en variabel. set(V newValue)-metoden setter en ny verdi for variabelen. På den annen side er lazySet(V newValue) lik set(), men kan utsette oppdateringen av verdien, og gir en ordnet oppdatering i visse situasjoner.
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) oppdaterer verdien hvis nåværende verdi samsvarer med forventet verdi. Den returnerer true hvis oppdateringen var vellykket, og false hvis nåværende verdi ikke samsvarte med forventet verdi. Til sammenligning setter metoden getAndSet(V newValue) en ny verdi og returnerer forrige verdi.
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()); } }
Metodene getAndIncrement() og getAndDecrement() øker eller reduserer den nåværende verdien med én og returnerer den forrige verdien. Disse metodene gjelder for numeriske atomiske variabler som AtomicInteger og AtomicLong. I motsetning til dette, øker eller reduserer metodene incrementAndGet() og decrementAndGet() også den nåværende verdien med én, men returnerer den nye verdien.
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 } }
Metoden getAndAdd(int delta) legger til den angitte verdien (delta) til den nåværende verdien og returnerer den forrige verdien. Denne metoden brukes med numeriske atomiske variabler. På den annen side legger metoden addAndGet(int delta) også til den angitte verdien (delta) til den nåværende verdien, men returnerer den nye verdien.
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 } }
Eksempler på bruk av atomiske variabler
Eksempel med AtomicInteger (AtomicLong, AtomicBoolean fungerer på samme måte, så det gis ikke egne eksempler for dem).
Oppgave: Implementere en teller som trygt økes av flere tråder.
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 ser brukte vi ikke noen synkronisering her, ettersom atomvariabelen selv gir denne funksjonen.
Dette eksempelet bruker AtomicInteger for å trygt øke telleren. Metoden getAndIncrement() returnerer først den nåværende verdien av variabelen, og øker deretter verdien med én. Dette skjer atomisk, noe som sikrer at variabelen oppdateres korrekt.
Eksempel med AtomicReference
Oppgave: Gi atomisk oppdatering av en referanse til et 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 brukes her for å oppdatere verdien til en strengreferanse atomisk. Metoden getAndSet() setter den nye verdien atomisk og returnerer den forrige verdien.
I motsetning til vanlige variabler, som krever ekstra synkronisering, benytter atomiske variabler lavnivå-primitiver for å minimere overhead og forbedre ytelsen. Dette gjør dem spesielt godt egnet for systemer med høy grad av samtidighet.
1. Hva er fordelen med å bruke atomiske variabler i flertrådede programmer?
2. Hvilken metode for atomiske variabler gir en atomisk endring av verdien hvis den nåværende verdien er lik forventet verdi?
3. Hva garanterer set()-metoden for atomiske variabler?
Takk for tilbakemeldingene dine!