Що робить 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 список атомних класів та відмінне пояснення того, як вони працюють (щойно дізналися, що вони без замків, тому вони мають перевагу перед блокуваннями або синхронізованими блоками)
booleanvar, ми повинні вибрати 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атомно.
Обидва мають однакову концепцію, але в атомному булевому режимі він надасть атомність операції, якщо перемикач процесора відбувається між ними.