Що робить AtomicBoolean, що нестабільний бул не може досягти?
Що робить AtomicBoolean, що нестабільний бул не може досягти?
Відповіді:
Вони просто абсолютно різні. Розглянемо цей приклад volatile
цілого числа:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Якщо дві нитки викликають функцію одночасно, i
може бути 5 після, оскільки складений код буде дещо подібний до цього (за винятком того, що ви не можете синхронізуватись int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Якщо змінна є мінливою, кожен атомний доступ до неї синхронізується, але не завжди очевидно, що насправді кваліфікується як атомний доступ. З Atomic*
об’єктом гарантується, що кожен метод "атомний".
Таким чином, якщо ви використовуєте AtomicInteger
і getAndAdd(int delta)
, ви можете бути впевнені, що результат буде 10
. Таким же чином, якщо обидва потоки одночасно відміняють boolean
змінну, з a AtomicBoolean
ви можете бути впевнені, що вона має вихідне значення згодом, з a volatile boolean
, ви не можете.
Отже, коли у вас є декілька потоків, що змінюють поле, вам потрібно зробити його атомним або використовувати явну синхронізацію.
Мета volatile
- інша. Розглянемо цей приклад
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Якщо у вас запущена нитка loop()
та інший потік дзвінка stop()
, ви можете зіткнутися з нескінченним циклом, якщо ви пропустите volatile
, оскільки перший потік може кешувати значення stop. Тут volatile
послуговує підказкою компілятору бути трохи обережнішими з оптимізаціями.
volatile
. Йдеться про volatile boolean
проти AtomicBoolean
.
volatile boolean
достатньо. Якщо також є багато письменників, можливо, вам знадобиться AtomicBoolean
.
Я використовую мінливі поля, коли згадане поле ТОЛЬКО ОНОВЛЕНО його потоком власника, а значення читається лише іншими потоками, ви можете вважати це сценарієм публікації / підписки, де багато спостерігачів, але лише один видавець. Однак якщо ці спостерігачі повинні виконувати певну логіку, засновану на значенні поля, а потім відштовхувати нове значення, то я переходжу до Atomic * vars або замків або синхронізованих блоків, що мені більше підходить. У багатьох паралельних сценаріях воно зводиться, щоб отримати значення, порівняти його з іншим та оновити, якщо необхідно, звідси методи порівняння та getAndSet, присутні в класах Atomic *.
Перевірте JavaDocs пакету java.util.concurrent.atomic список атомних класів та відмінне пояснення того, як вони працюють (щойно дізналися, що вони без замків, тому вони мають перевагу перед блокуваннями або синхронізованими блоками)
boolean
var, ми повинні вибрати volatile boolean
.
Ви не можете цього робити compareAndSet
, getAndSet
як атомна операція з летючим булевим (якщо, звичайно, не синхронізувати).
AtomicBoolean
має методи, які виконують свої складові операції атомно і без необхідності використовувати synchronized
блок. З іншого боку, volatile boolean
виконувати складені операції можна лише тоді, коли це робиться в synchronized
блоці.
Ефекти пам’яті читання / письма на volatile boolean
ідентичні відповідно get
і set
методам AtomicBoolean
відповідно.
Наприклад, compareAndSet
метод атомарно виконає наступне (без synchronized
блоку):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Отже, compareAndSet
метод дозволить вам написати код, який гарантовано виконується лише один раз, навіть коли він викликається з декількох потоків. Наприклад:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Гарантується, що сповістить слухача лише один раз (якщо припустимо, що жоден інший потік не AtomicBoolean
повертається назад false
після його встановлення true
).
volatile
ключові слова гарантують, що відбувається раніше, ніж зв'язок між потоками, що діляться цією змінною. Це не гарантує вам, що 2 або більше потоків не перебиватимуть один одного під час доступу до цієї булевої змінної.
Atomic*
Клас обгортання volatile
поля.
Летючий бул проти AtomicBoolean
Класи Atomic * обгортають летючий примітив одного типу. З джерела:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Отже, якщо все, що ви робите, це отримання та встановлення Atomic *, то ви також можете просто замінити його мінливе поле.
Що робить AtomicBoolean, що нестабільний бул не може досягти?
Класи Atomic * дають вам методи, що забезпечують більш досконалі функціональні можливості, такі як incrementAndGet()
, compareAndSet()
та інші, які реалізують кілька операцій (get / increment / set, test / set) без блокування. Ось чому класи Atomic * настільки потужні.
Наприклад, якщо декілька потоків використовують наступний код, використовуються ++
умови гонки, оскільки ++
це фактично: get, збільшення та set.
private volatile value;
...
// race conditions here
value++;
Однак наступний код буде безпечно працювати в багатопотоковому середовищі без блокування:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
Важливо також зазначити, що обертання мінливого поля за допомогою класу Atomic * є хорошим способом інкапсуляції критичного спільного ресурсу з точки зору об’єкта. Це означає, що розробники не можуть просто розібратися з полем, припускаючи, що він не є спільним, можливо, вводить проблеми з полем ++; або інший код, який представляє умови перегонів.
Якщо існує декілька потоків, що мають доступ до змінної рівня класу, то кожен потік може зберігати копію цієї змінної у своєму кеш-пам'яті потоку.
Здійснення змінної мінливою не дозволить потокам зберігати копію змінної в потоковому кеш-пам'яті.
Атомні змінні різні, і вони дозволяють атомну модифікацію своїх значень.
Булевий примітивний тип є атомним для операцій запису та читання, мінливі гарантії принципу "раніше". Тож якщо вам потрібні прості get () і set (), то вам не потрібен AtomicBoolean.
З іншого боку, якщо вам потрібно здійснити деяку перевірку перед встановленням значення змінної, наприклад, "якщо true тоді встановлено значення false", вам потрібно виконати цю операцію також атомним шляхом, і в цьому випадку використовуйте CompareAndSet та інші методи, передбачені AtomicBoolean, оскільки якщо ви спробуєте реалізувати цю логіку з летючим булевим, вам знадобиться деяка синхронізація, щоб переконатися, що значення не змінилося між get і set.
Запам’ятайте IDIOM -
ЧИТАЙТЕ - МОДИФІЙУЙТИ - ЗАПИСЬ, цього не можна досягти непостійним
volatile
працює лише в тих випадках, коли власний потік має можливість оновити значення поля, а інші потоки можуть читати лише.
Якщо у вас є лише один потік, що модифікує ваш булевий, ви можете використовуватиstop
мінливу булеву (зазвичай це робиться для визначення змінної, що перевіряється в головному циклі потоку).
Однак якщо у вас є кілька потоків, що модифікують булеві, вам слід скористатися AtomicBoolean
. Крім того, наступний код не є безпечним:
boolean r = !myVolatileBoolean;
Ця операція виконується в два етапи:
Якщо інший потік змінить значення між #1
і 2#
, ви можете отримати неправильний результат. AtomicBoolean
методи уникають цієї проблеми, роблячи кроки #1
та #2
атомно.
Обидва мають однакову концепцію, але в атомному булевому режимі він надасть атомність операції, якщо перемикач процесора відбувається між ними.