Летючий бул проти AtomicBoolean


244

Що робить AtomicBoolean, що нестабільний бул не може досягти?


16
Я шукав більш нюансовану відповідь: "які обмеження у кожного?". Наприклад, якщо є прапор, встановлений одним потоком і читається одним або кількома іншими, AtomicBoolean немає необхідності. Однак, як я бачу з цими відповідями, якщо потоки мають спільну змінну в декількох потоках, можуть записувати і діють на результат їх читання, AtomicBoolean приносить в дію незаблокувальні операції типу CAS. Насправді я тут навчусь зовсім небагато. Будемо сподіватися, що й інші отримають користь.
JeffV


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

Відповіді:


99

Вони просто абсолютно різні. Розглянемо цей приклад 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послуговує підказкою компілятору бути трохи обережнішими з оптимізаціями.


84
-1: ти наводиш приклади, але насправді не пояснюєш різницю між летючим і Atomicxxxx.
Jason S

70
Питання не в тому volatile. Йдеться про volatile booleanпроти AtomicBoolean.
долмен

26
-1: запитання, специфічно задане про булеве, що є унікальним випадком порівняно з іншими типами даних і має бути пояснене безпосередньо.
Джон Хагер

8
@ sgp15 Це стосується синхронізації з Java 5.
Man of One Way

6
Якщо булеве значення читається багатьма потоками, але записується лише однією ниткою, то volatile booleanдостатньо. Якщо також є багато письменників, можливо, вам знадобиться AtomicBoolean.
StvnBrkdll

263

Я використовую мінливі поля, коли згадане поле ТОЛЬКО ОНОВЛЕНО його потоком власника, а значення читається лише іншими потоками, ви можете вважати це сценарієм публікації / підписки, де багато спостерігачів, але лише один видавець. Однак якщо ці спостерігачі повинні виконувати певну логіку, засновану на значенні поля, а потім відштовхувати нове значення, то я переходжу до Atomic * vars або замків або синхронізованих блоків, що мені більше підходить. У багатьох паралельних сценаріях воно зводиться, щоб отримати значення, порівняти його з іншим та оновити, якщо необхідно, звідси методи порівняння та getAndSet, присутні в класах Atomic *.

Перевірте JavaDocs пакету java.util.concurrent.atomic список атомних класів та відмінне пояснення того, як вони працюють (щойно дізналися, що вони без замків, тому вони мають перевагу перед блокуваннями або синхронізованими блоками)


1
@ksl Я думаю, @teto хочу описати, що якщо лише одна нитка змінить booleanvar, ми повинні вибрати volatile boolean.
znlyj

2
Відмінна резюме.
Равіндра бабу

56

Ви не можете цього робити compareAndSet, getAndSetяк атомна операція з летючим булевим (якщо, звичайно, не синхронізувати).


6
Це правда, але хіба це не буде досить рідкісною вимогою до булевих?
Робін

1
@Robin подумає про те, щоб використовувати його для управління ледачим викликом методу ініціалізації.
Устаман Сангат

Власне, я думаю, що це один з основних випадків використання.
crazy4jesus

42

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).


14

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


14
Бульний (як і у примітивного типу) доступ є атомним на Java. І читання, і завдання. Тож жоден інший потік не буде "переривати" булеві операції.
Maciej Biłas

1
Вибачте, але як це відповідає на питання? Atomic*Клас обгортання volatileполя.
Сірий

Чи не кешовані процесори є основним фактором для встановлення нестабільних? Щоб переконатись, що прочитане значення є насправді тим, що було встановлено останнім часом
Jocull

8

Летючий бул проти 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 * є хорошим способом інкапсуляції критичного спільного ресурсу з точки зору об’єкта. Це означає, що розробники не можуть просто розібратися з полем, припускаючи, що він не є спільним, можливо, вводить проблеми з полем ++; або інший код, який представляє умови перегонів.


5

Якщо існує декілька потоків, що мають доступ до змінної рівня класу, то кожен потік може зберігати копію цієї змінної у своєму кеш-пам'яті потоку.

Здійснення змінної мінливою не дозволить потокам зберігати копію змінної в потоковому кеш-пам'яті.

Атомні змінні різні, і вони дозволяють атомну модифікацію своїх значень.


4

Булевий примітивний тип є атомним для операцій запису та читання, мінливі гарантії принципу "раніше". Тож якщо вам потрібні прості get () і set (), то вам не потрібен AtomicBoolean.

З іншого боку, якщо вам потрібно здійснити деяку перевірку перед встановленням значення змінної, наприклад, "якщо true тоді встановлено значення false", вам потрібно виконати цю операцію також атомним шляхом, і в цьому випадку використовуйте CompareAndSet та інші методи, передбачені AtomicBoolean, оскільки якщо ви спробуєте реалізувати цю логіку з летючим булевим, вам знадобиться деяка синхронізація, щоб переконатися, що значення не змінилося між get і set.


3

Запам’ятайте IDIOM -

ЧИТАЙТЕ - МОДИФІЙУЙТИ - ЗАПИСЬ, цього не можна досягти непостійним


2
Короткий, хрусткий і до речі. volatileпрацює лише в тих випадках, коли власний потік має можливість оновити значення поля, а інші потоки можуть читати лише.
Chaklader Asfak Arefe

3

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

Однак якщо у вас є кілька потоків, що модифікують булеві, вам слід скористатися AtomicBoolean. Крім того, наступний код не є безпечним:

boolean r = !myVolatileBoolean;

Ця операція виконується в два етапи:

  1. Булове значення читається.
  2. Булове значення записується.

Якщо інший потік змінить значення між #1і 2#, ви можете отримати неправильний результат. AtomicBooleanметоди уникають цієї проблеми, роблячи кроки #1та #2атомно.


-1

Обидва мають однакову концепцію, але в атомному булевому режимі він надасть атомність операції, якщо перемикач процесора відбувається між ними.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.