Атомарні Змінні
Ми вже розглянули поняття атомарності та проблеми, які вона може спричинити, у першому розділі цього курсу. Тоді ми вирішували це питання за допомогою синхронізованих блоків або методів. Тепер розглянемо, як досягти такого ж результату простіше, використовуючи атомарний клас.
Що таке атомарні змінні?
Атомарні змінні гарантують, що операції (читання, запис, інкремент) над змінними виконуються атомарно, тобто виконуються безперервно та безпечно у багатопотоковому середовищі. Це забезпечує повне завершення операції без можливості втручання інших потоків під час її виконання.
Навіщо потрібні атомарні змінні?
Без використання атомарних змінних або інших механізмів синхронізації операції, такі як інкремент (++), можуть бути небезпечними. Наприклад, коли декілька потоків одночасно звертаються до однієї змінної, оновлення можуть бути втрачені, що призводить до некоректних результатів. Атомарні змінні вирішують цю проблему, гарантуючи, що операції над ними виконуються послідовно.
Раніше ми розглядали цю проблему, коли операція інкременту розбивалася на три кроки (читання, інкремент, запис), але з атомарними змінними все виконується за одну операцію!
Типи атомарних змінних у Java
Загалом існує багато атомарних реалізацій, і ми не розглядатимемо їх усі тут, оскільки це займе надто багато часу.
Java надає кілька класів атомарних змінних у пакеті java.util.concurrent.atomic, кожен з яких призначений для роботи з конкретним типом даних:
AtomicInteger: для атомарних операцій над int;AtomicLong: для атомарних операцій над long;AtomicBoolean: для атомарних операцій над boolean;AtomicReference<V>: для атомарних операцій над об'єктами (узагальнений тип).
Методи
Метод get() повертає поточне значення змінної. Метод set(V newValue) встановлює нове значення для змінної. Натомість, lazySet(V newValue) подібний до set(), але може відкласти оновлення значення, забезпечуючи впорядковане оновлення у певних ситуаціях.
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()); } }
Метод compareAndSet(V expect, V update) оновлює значення, якщо поточне значення збігається з очікуваним. Повертає true, якщо оновлення було успішним, і false, якщо поточне значення не збіглося з очікуваним. На відміну від цього, метод getAndSet(V newValue) встановлює нове значення та повертає попереднє значення.
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() та getAndDecrement() збільшують або зменшують поточне значення на одиницю та повертають попереднє значення. Ці методи застосовуються до числових атомарних змінних, таких як AtomicInteger та AtomicLong. На відміну від них, методи incrementAndGet() та decrementAndGet() також збільшують або зменшують поточне значення на одиницю, але повертають нове значення.
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) додає вказане значення (delta) до поточного значення та повертає попереднє значення. Цей метод використовується з числовими атомарними змінними. З іншого боку, метод addAndGet(int delta) також додає вказане значення (delta) до поточного значення, але повертає нове значення.
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 } }
Приклади використання атомарних змінних
Приклад з AtomicInteger (AtomicLong, AtomicBoolean працюють аналогічно, тому окремі приклади для них не наводяться).
Завдання: Реалізувати лічильник, який безпечно інкрементується декількома потоками.
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(); } } }
Як бачите, тут не використовується жодна синхронізація, оскільки атомарна змінна забезпечує цю функцію.
У цьому прикладі використовується AtomicInteger для безпечного інкрементування лічильника. Метод getAndIncrement() спочатку повертає поточне значення змінної, а потім збільшує його на одиницю. Це відбувається атомарно, що гарантує коректне оновлення змінної.
Приклад з AtomicReference
Завдання: Забезпечити атомарне оновлення посилання на об'єкт.
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 використовується тут для атомарного оновлення значення посилання на рядок. Метод getAndSet() атомарно встановлює нове значення та повертає попереднє значення.
На відміну від звичайних змінних, які потребують додаткової синхронізації, атомарні змінні використовують низькорівневі примітиви для мінімізації накладних витрат і підвищення продуктивності. Це робить їх особливо придатними для систем з високим рівнем конкурентності.
1. Яка перевага використання атомарних змінних у багатопотоковому програмуванні?
2. Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?
3. Що гарантує метод set() в атомарних змінних?
Дякуємо за ваш відгук!
Запитати АІ
Запитати АІ
Запитайте про що завгодно або спробуйте одне із запропонованих запитань, щоб почати наш чат
Awesome!
Completion rate improved to 3.33
Атомарні Змінні
Свайпніть щоб показати меню
Ми вже розглянули поняття атомарності та проблеми, які вона може спричинити, у першому розділі цього курсу. Тоді ми вирішували це питання за допомогою синхронізованих блоків або методів. Тепер розглянемо, як досягти такого ж результату простіше, використовуючи атомарний клас.
Що таке атомарні змінні?
Атомарні змінні гарантують, що операції (читання, запис, інкремент) над змінними виконуються атомарно, тобто виконуються безперервно та безпечно у багатопотоковому середовищі. Це забезпечує повне завершення операції без можливості втручання інших потоків під час її виконання.
Навіщо потрібні атомарні змінні?
Без використання атомарних змінних або інших механізмів синхронізації операції, такі як інкремент (++), можуть бути небезпечними. Наприклад, коли декілька потоків одночасно звертаються до однієї змінної, оновлення можуть бути втрачені, що призводить до некоректних результатів. Атомарні змінні вирішують цю проблему, гарантуючи, що операції над ними виконуються послідовно.
Раніше ми розглядали цю проблему, коли операція інкременту розбивалася на три кроки (читання, інкремент, запис), але з атомарними змінними все виконується за одну операцію!
Типи атомарних змінних у Java
Загалом існує багато атомарних реалізацій, і ми не розглядатимемо їх усі тут, оскільки це займе надто багато часу.
Java надає кілька класів атомарних змінних у пакеті java.util.concurrent.atomic, кожен з яких призначений для роботи з конкретним типом даних:
AtomicInteger: для атомарних операцій над int;AtomicLong: для атомарних операцій над long;AtomicBoolean: для атомарних операцій над boolean;AtomicReference<V>: для атомарних операцій над об'єктами (узагальнений тип).
Методи
Метод get() повертає поточне значення змінної. Метод set(V newValue) встановлює нове значення для змінної. Натомість, lazySet(V newValue) подібний до set(), але може відкласти оновлення значення, забезпечуючи впорядковане оновлення у певних ситуаціях.
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()); } }
Метод compareAndSet(V expect, V update) оновлює значення, якщо поточне значення збігається з очікуваним. Повертає true, якщо оновлення було успішним, і false, якщо поточне значення не збіглося з очікуваним. На відміну від цього, метод getAndSet(V newValue) встановлює нове значення та повертає попереднє значення.
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() та getAndDecrement() збільшують або зменшують поточне значення на одиницю та повертають попереднє значення. Ці методи застосовуються до числових атомарних змінних, таких як AtomicInteger та AtomicLong. На відміну від них, методи incrementAndGet() та decrementAndGet() також збільшують або зменшують поточне значення на одиницю, але повертають нове значення.
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) додає вказане значення (delta) до поточного значення та повертає попереднє значення. Цей метод використовується з числовими атомарними змінними. З іншого боку, метод addAndGet(int delta) також додає вказане значення (delta) до поточного значення, але повертає нове значення.
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 } }
Приклади використання атомарних змінних
Приклад з AtomicInteger (AtomicLong, AtomicBoolean працюють аналогічно, тому окремі приклади для них не наводяться).
Завдання: Реалізувати лічильник, який безпечно інкрементується декількома потоками.
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(); } } }
Як бачите, тут не використовується жодна синхронізація, оскільки атомарна змінна забезпечує цю функцію.
У цьому прикладі використовується AtomicInteger для безпечного інкрементування лічильника. Метод getAndIncrement() спочатку повертає поточне значення змінної, а потім збільшує його на одиницю. Це відбувається атомарно, що гарантує коректне оновлення змінної.
Приклад з AtomicReference
Завдання: Забезпечити атомарне оновлення посилання на об'єкт.
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 використовується тут для атомарного оновлення значення посилання на рядок. Метод getAndSet() атомарно встановлює нове значення та повертає попереднє значення.
На відміну від звичайних змінних, які потребують додаткової синхронізації, атомарні змінні використовують низькорівневі примітиви для мінімізації накладних витрат і підвищення продуктивності. Це робить їх особливо придатними для систем з високим рівнем конкурентності.
1. Яка перевага використання атомарних змінних у багатопотоковому програмуванні?
2. Який метод атомарної змінної забезпечує атомарну зміну значення, якщо поточне значення збігається з очікуваним?
3. Що гарантує метод set() в атомарних змінних?
Дякуємо за ваш відгук!