Notice: This page requires JavaScript to function properly.
Please enable JavaScript in your browser settings or update your browser.
Вивчайте Атомарні Змінні | Механізми Синхронізації Високого Рівня
Багатопотоковість у Java

bookАтомарні Змінні

Ми вже розглянули поняття атомарності та проблеми, які вона може спричинити, у першому розділі цього курсу. Тоді ми вирішували це питання за допомогою синхронізованих блоків або методів. Тепер розглянемо, як досягти такого ж результату простіше, використовуючи атомарний клас.

Що таке атомарні змінні?

Атомарні змінні гарантують, що операції (читання, запис, інкремент) над змінними виконуються атомарно, тобто виконуються безперервно та безпечно у багатопотоковому середовищі. Це забезпечує повне завершення операції без можливості втручання інших потоків під час її виконання.

Навіщо потрібні атомарні змінні?

Без використання атомарних змінних або інших механізмів синхронізації операції, такі як інкремент (++), можуть бути небезпечними. Наприклад, коли декілька потоків одночасно звертаються до однієї змінної, оновлення можуть бути втрачені, що призводить до некоректних результатів. Атомарні змінні вирішують цю проблему, гарантуючи, що операції над ними виконуються послідовно.

Раніше ми розглядали цю проблему, коли операція інкременту розбивалася на три кроки (читання, інкремент, запис), але з атомарними змінними все виконується за одну операцію!

Типи атомарних змінних у Java

Note
Примітка

Загалом існує багато атомарних реалізацій, і ми не розглядатимемо їх усі тут, оскільки це займе надто багато часу.

Java надає кілька класів атомарних змінних у пакеті java.util.concurrent.atomic, кожен з яких призначений для роботи з конкретним типом даних:

  • AtomicInteger: для атомарних операцій над int;
  • AtomicLong: для атомарних операцій над long;
  • AtomicBoolean: для атомарних операцій над boolean;
  • AtomicReference<V>: для атомарних операцій над об'єктами (узагальнений тип).

Методи

Метод get() повертає поточне значення змінної. Метод set(V newValue) встановлює нове значення для змінної. Натомість, lazySet(V newValue) подібний до set(), але може відкласти оновлення значення, забезпечуючи впорядковане оновлення у певних ситуаціях.

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

Метод compareAndSet(V expect, V update) оновлює значення, якщо поточне значення збігається з очікуваним. Повертає true, якщо оновлення було успішним, і false, якщо поточне значення не збіглося з очікуваним. На відміну від цього, метод getAndSet(V newValue) встановлює нове значення та повертає попереднє значення.

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

Методи getAndIncrement() та getAndDecrement() збільшують або зменшують поточне значення на одиницю та повертають попереднє значення. Ці методи застосовуються до числових атомарних змінних, таких як AtomicInteger та AtomicLong. На відміну від них, методи incrementAndGet() та decrementAndGet() також збільшують або зменшують поточне значення на одиницю, але повертають нове значення.

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

Метод getAndAdd(int delta) додає вказане значення (delta) до поточного значення та повертає попереднє значення. Цей метод використовується з числовими атомарними змінними. З іншого боку, метод addAndGet(int delta) також додає вказане значення (delta) до поточного значення, але повертає нове значення.

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

Приклади використання атомарних змінних

Приклад з AtomicInteger (AtomicLong, AtomicBoolean працюють аналогічно, тому окремі приклади для них не наводяться).

Завдання: Реалізувати лічильник, який безпечно інкрементується декількома потоками.

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

Як бачите, тут не використовується жодна синхронізація, оскільки атомарна змінна забезпечує цю функцію.

У цьому прикладі використовується AtomicInteger для безпечного інкрементування лічильника. Метод getAndIncrement() спочатку повертає поточне значення змінної, а потім збільшує його на одиницю. Це відбувається атомарно, що гарантує коректне оновлення змінної.

Приклад з AtomicReference

Завдання: Забезпечити атомарне оновлення посилання на об'єкт.

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 використовується тут для атомарного оновлення значення посилання на рядок. Метод getAndSet() атомарно встановлює нове значення та повертає попереднє значення.

Note
Примітка

На відміну від звичайних змінних, які потребують додаткової синхронізації, атомарні змінні використовують низькорівневі примітиви для мінімізації накладних витрат і підвищення продуктивності. Це робить їх особливо придатними для систем з високим рівнем конкурентності.

1. Яка перевага використання атомарних змінних у багатопотоковому програмуванні?

2. Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?

3. Що гарантує метод set() в атомарних змінних?

question mark

Яка перевага використання атомарних змінних у багатопотоковому програмуванні?

Select the correct answer

question mark

Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?

Select the correct answer

question mark

Що гарантує метод set() в атомарних змінних?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 5

Запитати АІ

expand

Запитати АІ

ChatGPT

Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат

Awesome!

Completion rate improved to 3.33

bookАтомарні Змінні

Свайпніть щоб показати меню

Ми вже розглянули поняття атомарності та проблеми, які вона може спричинити, у першому розділі цього курсу. Тоді ми вирішували це питання за допомогою синхронізованих блоків або методів. Тепер розглянемо, як досягти такого ж результату простіше, використовуючи атомарний клас.

Що таке атомарні змінні?

Атомарні змінні гарантують, що операції (читання, запис, інкремент) над змінними виконуються атомарно, тобто виконуються безперервно та безпечно у багатопотоковому середовищі. Це забезпечує повне завершення операції без можливості втручання інших потоків під час її виконання.

Навіщо потрібні атомарні змінні?

Без використання атомарних змінних або інших механізмів синхронізації операції, такі як інкремент (++), можуть бути небезпечними. Наприклад, коли декілька потоків одночасно звертаються до однієї змінної, оновлення можуть бути втрачені, що призводить до некоректних результатів. Атомарні змінні вирішують цю проблему, гарантуючи, що операції над ними виконуються послідовно.

Раніше ми розглядали цю проблему, коли операція інкременту розбивалася на три кроки (читання, інкремент, запис), але з атомарними змінними все виконується за одну операцію!

Типи атомарних змінних у Java

Note
Примітка

Загалом існує багато атомарних реалізацій, і ми не розглядатимемо їх усі тут, оскільки це займе надто багато часу.

Java надає кілька класів атомарних змінних у пакеті java.util.concurrent.atomic, кожен з яких призначений для роботи з конкретним типом даних:

  • AtomicInteger: для атомарних операцій над int;
  • AtomicLong: для атомарних операцій над long;
  • AtomicBoolean: для атомарних операцій над boolean;
  • AtomicReference<V>: для атомарних операцій над об'єктами (узагальнений тип).

Методи

Метод get() повертає поточне значення змінної. Метод set(V newValue) встановлює нове значення для змінної. Натомість, lazySet(V newValue) подібний до set(), але може відкласти оновлення значення, забезпечуючи впорядковане оновлення у певних ситуаціях.

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

Метод compareAndSet(V expect, V update) оновлює значення, якщо поточне значення збігається з очікуваним. Повертає true, якщо оновлення було успішним, і false, якщо поточне значення не збіглося з очікуваним. На відміну від цього, метод getAndSet(V newValue) встановлює нове значення та повертає попереднє значення.

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

Методи getAndIncrement() та getAndDecrement() збільшують або зменшують поточне значення на одиницю та повертають попереднє значення. Ці методи застосовуються до числових атомарних змінних, таких як AtomicInteger та AtomicLong. На відміну від них, методи incrementAndGet() та decrementAndGet() також збільшують або зменшують поточне значення на одиницю, але повертають нове значення.

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

Метод getAndAdd(int delta) додає вказане значення (delta) до поточного значення та повертає попереднє значення. Цей метод використовується з числовими атомарними змінними. З іншого боку, метод addAndGet(int delta) також додає вказане значення (delta) до поточного значення, але повертає нове значення.

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

Приклади використання атомарних змінних

Приклад з AtomicInteger (AtomicLong, AtomicBoolean працюють аналогічно, тому окремі приклади для них не наводяться).

Завдання: Реалізувати лічильник, який безпечно інкрементується декількома потоками.

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

Як бачите, тут не використовується жодна синхронізація, оскільки атомарна змінна забезпечує цю функцію.

У цьому прикладі використовується AtomicInteger для безпечного інкрементування лічильника. Метод getAndIncrement() спочатку повертає поточне значення змінної, а потім збільшує його на одиницю. Це відбувається атомарно, що гарантує коректне оновлення змінної.

Приклад з AtomicReference

Завдання: Забезпечити атомарне оновлення посилання на об'єкт.

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 використовується тут для атомарного оновлення значення посилання на рядок. Метод getAndSet() атомарно встановлює нове значення та повертає попереднє значення.

Note
Примітка

На відміну від звичайних змінних, які потребують додаткової синхронізації, атомарні змінні використовують низькорівневі примітиви для мінімізації накладних витрат і підвищення продуктивності. Це робить їх особливо придатними для систем з високим рівнем конкурентності.

1. Яка перевага використання атомарних змінних у багатопотоковому програмуванні?

2. Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?

3. Що гарантує метод set() в атомарних змінних?

question mark

Яка перевага використання атомарних змінних у багатопотоковому програмуванні?

Select the correct answer

question mark

Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?

Select the correct answer

question mark

Що гарантує метод set() в атомарних змінних?

Select the correct answer

Все було зрозуміло?

Як ми можемо покращити це?

Дякуємо за ваш відгук!

Секція 3. Розділ 5
some-alt