Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Leer Atomische Variabelen | High-Level Synchronisatiemechanismen
Quizzes & Challenges
Quizzes
Challenges
/
Multithreading in Java

bookAtomische Variabelen

We hebben al besproken wat atomiciteit is en de problemen die het kan veroorzaken in het eerste deel van deze cursus. Destijds hebben we het probleem aangepakt met behulp van gesynchroniseerde blokken of methoden. Nu gaan we onderzoeken hoe we hetzelfde resultaat eenvoudiger kunnen bereiken door gebruik te maken van een atomische klasse.

Wat zijn atomische variabelen?

Atomische variabelen zorgen ervoor dat bewerkingen (lezen, schrijven, verhogen) op variabelen atomisch worden uitgevoerd, wat betekent dat ze ononderbroken en veilig in een multithreaded omgeving plaatsvinden. Dit garandeert dat de bewerking volledig wordt afgerond, zonder dat andere threads tijdens de uitvoering kunnen ingrijpen.

Waarom hebben we atomaire variabelen nodig?

Zonder het gebruik van atomaire variabelen of andere synchronisatiemechanismen kunnen bewerkingen zoals verhogen (++) onveilig zijn. Wanneer bijvoorbeeld meerdere threads gelijktijdig toegang hebben tot dezelfde variabele, kunnen updates verloren gaan, wat leidt tot onjuiste resultaten. Atomaire variabelen lossen dit probleem op door ervoor te zorgen dat bewerkingen op deze variabelen sequentieel worden uitgevoerd.

We hebben dit probleem eerder besproken toen de verhoogbewerking werd opgesplitst in drie stappen (lezen, verhogen, schrijven), maar met atomaire variabelen gebeurt dit allemaal in één bewerking!

Typen atomaire variabelen in Java

Note
Opmerking

In het algemeen zijn er veel atomische implementaties, en we zullen ze hier niet allemaal behandelen, omdat dit te veel tijd zou kosten.

Java biedt verschillende atomische variabelen klassen in het pakket java.util.concurrent.atomic, elk ontworpen voor een specifiek gegevenstype:

  • AtomicInteger: voor atomische bewerkingen op int;
  • AtomicLong: voor atomische bewerkingen op long;
  • AtomicBoolean: voor atomische bewerkingen op boolean;
  • AtomicReference<V>: voor atomische bewerkingen op objecten (generiek type).

Methoden

De methode get() retourneert de huidige waarde van een variabele. De methode set(V newValue) stelt een nieuwe waarde in voor de variabele. Daarentegen lijkt lazySet(V newValue) op set(), maar kan het bijwerken van de waarde uitstellen, waardoor in bepaalde situaties een geordende update mogelijk is.

Main.java

Main.java

copy
123456789101112131415161718192021
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()); } }

De methode compareAndSet(V expect, V update) werkt de waarde bij als de huidige waarde overeenkomt met de verwachte waarde. Deze retourneert true als de bijwerking geslaagd is, en false als de huidige waarde niet overeenkwam met de verwachte waarde. Daarentegen stelt de methode getAndSet(V newValue) een nieuwe waarde in en geeft de vorige waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324
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()); } }

De methoden getAndIncrement() en getAndDecrement() verhogen of verlagen de huidige waarde met één en geven de vorige waarde terug. Deze methoden zijn van toepassing op numerieke atomische variabelen zoals AtomicInteger en AtomicLong. Daarentegen verhogen of verlagen de methoden incrementAndGet() en decrementAndGet() ook de huidige waarde met één, maar geven de nieuwe waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
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 } }

De methode getAndAdd(int delta) telt de opgegeven waarde (delta) op bij de huidige waarde en geeft de vorige waarde terug. Deze methode wordt gebruikt met numerieke atomische variabelen. Aan de andere kant telt de methode addAndGet(int delta) ook de opgegeven waarde (delta) op bij de huidige waarde, maar geeft de nieuwe waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
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 } }

Voorbeelden van het gebruik van atomische variabelen

Voorbeeld met AtomicInteger (AtomicLong, AtomicBoolean werken op dezelfde manier, daarom worden hiervoor geen aparte voorbeelden gegeven).

Opdracht: Een teller implementeren die veilig wordt verhoogd door meerdere threads.

Main.java

Main.java

copy
1234567891011121314151617181920
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(); } } }

Zoals je kunt zien hebben we hier geen synchronisatie gebruikt, omdat de atomische variabele deze functie zelf biedt.

Dit voorbeeld gebruikt AtomicInteger om de teller veilig te verhogen. De methode getAndIncrement() geeft eerst de huidige waarde van de variabele terug en verhoogt deze vervolgens met één. Dit gebeurt atomisch, waardoor wordt gegarandeerd dat de variabele correct wordt bijgewerkt.

Voorbeeld met AtomicReference

Taak: Bieden van atomische bijwerking van een referentie naar een object.

Main.java

Main.java

copy
123456789101112131415161718192021222324
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 wordt hier gebruikt om de waarde van een stringreferentie atomair bij te werken. De methode getAndSet() stelt de nieuwe waarde atomair in en retourneert de vorige waarde.

Note
Opmerking

In tegenstelling tot reguliere variabelen, die extra *synchronisatie vereisen, maken atomische variabelen gebruik van laag-niveau primitieve bewerkingen om overhead te minimaliseren en de prestaties te verbeteren. Hierdoor zijn ze bijzonder geschikt voor systemen met hoge gelijktijdigheid.

1. Wat is het voordeel van het gebruik van atomische variabelen in multithreaded programmeren?

2. Welke methode van een atomische variabele zorgt voor een atomische wijziging van de waarde als de huidige waarde gelijk is aan de verwachte waarde?

3. Wat garandeert de set()-methode bij atomische variabelen?

question mark

Wat is het voordeel van het gebruik van atomische variabelen in multithreaded programmeren?

Select the correct answer

question mark

Welke methode van een atomische variabele zorgt voor een atomische wijziging van de waarde als de huidige waarde gelijk is aan de verwachte waarde?

Select the correct answer

question mark

Wat garandeert de set()-methode bij atomische variabelen?

Select the correct answer

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 5

Vraag AI

expand

Vraag AI

ChatGPT

Vraag wat u wilt of probeer een van de voorgestelde vragen om onze chat te starten.

Awesome!

Completion rate improved to 3.33

bookAtomische Variabelen

Veeg om het menu te tonen

We hebben al besproken wat atomiciteit is en de problemen die het kan veroorzaken in het eerste deel van deze cursus. Destijds hebben we het probleem aangepakt met behulp van gesynchroniseerde blokken of methoden. Nu gaan we onderzoeken hoe we hetzelfde resultaat eenvoudiger kunnen bereiken door gebruik te maken van een atomische klasse.

Wat zijn atomische variabelen?

Atomische variabelen zorgen ervoor dat bewerkingen (lezen, schrijven, verhogen) op variabelen atomisch worden uitgevoerd, wat betekent dat ze ononderbroken en veilig in een multithreaded omgeving plaatsvinden. Dit garandeert dat de bewerking volledig wordt afgerond, zonder dat andere threads tijdens de uitvoering kunnen ingrijpen.

Waarom hebben we atomaire variabelen nodig?

Zonder het gebruik van atomaire variabelen of andere synchronisatiemechanismen kunnen bewerkingen zoals verhogen (++) onveilig zijn. Wanneer bijvoorbeeld meerdere threads gelijktijdig toegang hebben tot dezelfde variabele, kunnen updates verloren gaan, wat leidt tot onjuiste resultaten. Atomaire variabelen lossen dit probleem op door ervoor te zorgen dat bewerkingen op deze variabelen sequentieel worden uitgevoerd.

We hebben dit probleem eerder besproken toen de verhoogbewerking werd opgesplitst in drie stappen (lezen, verhogen, schrijven), maar met atomaire variabelen gebeurt dit allemaal in één bewerking!

Typen atomaire variabelen in Java

Note
Opmerking

In het algemeen zijn er veel atomische implementaties, en we zullen ze hier niet allemaal behandelen, omdat dit te veel tijd zou kosten.

Java biedt verschillende atomische variabelen klassen in het pakket java.util.concurrent.atomic, elk ontworpen voor een specifiek gegevenstype:

  • AtomicInteger: voor atomische bewerkingen op int;
  • AtomicLong: voor atomische bewerkingen op long;
  • AtomicBoolean: voor atomische bewerkingen op boolean;
  • AtomicReference<V>: voor atomische bewerkingen op objecten (generiek type).

Methoden

De methode get() retourneert de huidige waarde van een variabele. De methode set(V newValue) stelt een nieuwe waarde in voor de variabele. Daarentegen lijkt lazySet(V newValue) op set(), maar kan het bijwerken van de waarde uitstellen, waardoor in bepaalde situaties een geordende update mogelijk is.

Main.java

Main.java

copy
123456789101112131415161718192021
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()); } }

De methode compareAndSet(V expect, V update) werkt de waarde bij als de huidige waarde overeenkomt met de verwachte waarde. Deze retourneert true als de bijwerking geslaagd is, en false als de huidige waarde niet overeenkwam met de verwachte waarde. Daarentegen stelt de methode getAndSet(V newValue) een nieuwe waarde in en geeft de vorige waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324
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()); } }

De methoden getAndIncrement() en getAndDecrement() verhogen of verlagen de huidige waarde met één en geven de vorige waarde terug. Deze methoden zijn van toepassing op numerieke atomische variabelen zoals AtomicInteger en AtomicLong. Daarentegen verhogen of verlagen de methoden incrementAndGet() en decrementAndGet() ook de huidige waarde met één, maar geven de nieuwe waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
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 } }

De methode getAndAdd(int delta) telt de opgegeven waarde (delta) op bij de huidige waarde en geeft de vorige waarde terug. Deze methode wordt gebruikt met numerieke atomische variabelen. Aan de andere kant telt de methode addAndGet(int delta) ook de opgegeven waarde (delta) op bij de huidige waarde, maar geeft de nieuwe waarde terug.

Main.java

Main.java

copy
123456789101112131415161718192021222324252627282930
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 } }

Voorbeelden van het gebruik van atomische variabelen

Voorbeeld met AtomicInteger (AtomicLong, AtomicBoolean werken op dezelfde manier, daarom worden hiervoor geen aparte voorbeelden gegeven).

Opdracht: Een teller implementeren die veilig wordt verhoogd door meerdere threads.

Main.java

Main.java

copy
1234567891011121314151617181920
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(); } } }

Zoals je kunt zien hebben we hier geen synchronisatie gebruikt, omdat de atomische variabele deze functie zelf biedt.

Dit voorbeeld gebruikt AtomicInteger om de teller veilig te verhogen. De methode getAndIncrement() geeft eerst de huidige waarde van de variabele terug en verhoogt deze vervolgens met één. Dit gebeurt atomisch, waardoor wordt gegarandeerd dat de variabele correct wordt bijgewerkt.

Voorbeeld met AtomicReference

Taak: Bieden van atomische bijwerking van een referentie naar een object.

Main.java

Main.java

copy
123456789101112131415161718192021222324
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 wordt hier gebruikt om de waarde van een stringreferentie atomair bij te werken. De methode getAndSet() stelt de nieuwe waarde atomair in en retourneert de vorige waarde.

Note
Opmerking

In tegenstelling tot reguliere variabelen, die extra *synchronisatie vereisen, maken atomische variabelen gebruik van laag-niveau primitieve bewerkingen om overhead te minimaliseren en de prestaties te verbeteren. Hierdoor zijn ze bijzonder geschikt voor systemen met hoge gelijktijdigheid.

1. Wat is het voordeel van het gebruik van atomische variabelen in multithreaded programmeren?

2. Welke methode van een atomische variabele zorgt voor een atomische wijziging van de waarde als de huidige waarde gelijk is aan de verwachte waarde?

3. Wat garandeert de set()-methode bij atomische variabelen?

question mark

Wat is het voordeel van het gebruik van atomische variabelen in multithreaded programmeren?

Select the correct answer

question mark

Welke methode van een atomische variabele zorgt voor een atomische wijziging van de waarde als de huidige waarde gelijk is aan de verwachte waarde?

Select the correct answer

question mark

Wat garandeert de set()-methode bij atomische variabelen?

Select the correct answer

Was alles duidelijk?

Hoe kunnen we het verbeteren?

Bedankt voor je feedback!

Sectie 3. Hoofdstuk 5
some-alt