Коротко — из-за Java Memory Model чтение/запись поля в одном потоке не обязательно сразу видно в другом: компилятор/процессор/кэш могут сохранить локальную копию, а оптимизатор может «вынести» чтение за цикл. Поэтому код boolean stop = false; \text{boolean stop = false;} boolean stop = false;
// поток 1 while(!stop) { ... } \text{while(!stop) \{ ... \}} while(!stop) { ... }
// поток 2 stop = true; \text{stop = true;} stop = true; может привести к тому, что поток 1 никогда не увидит изменение и застрянет в цикле. Почему volatile или synchronized это меняют - volatile: - Объявление volatile boolean stop; \text{volatile boolean stop;} volatile boolean stop; запрещает кэширование значения переменной в регистре/локальном кешe: чтение всегда берётся из основной памяти. - Гарантирует семантику happens-before: запись stop = true \text{stop = true} stop = true happens-before последующего чтения stop \text{stop} stop в другом потоке — значит изменение гарантированно увидят. - Также запрещает определённые перестановки компилятором/процессором относительно этой переменной. - Применение: простой флаг для уведомления — подходит. - synchronized: - Вход и выход из монитора (synchronized) создают happens-before: выход (unlock) одного потока happens-before вход (lock) другого потока. - Если оба потока читают/пишут под тем же lock, то видимость гарантирована (и дополнительно обеспечена взаимная исключённость). - Более тяжелый по стоимости, но нужен, если помимо видимости требуется атомарность/несколько связанных полей. Примеры (упрощённо) 1) Проблемный (нет гарантии): class Flag { boolean stop = false; } 2) volatile (решает проблему): class Flag { volatile boolean stop = false; } 3) synchronized (решает через блоки): class Flag { boolean stop = false; Object lock = new Object(); } writer: synchronized(lock) { stop = true; } reader: while (true) { synchronized(lock) { if (stop) break; } // ... } Рекомендация: для простого флага используйте volatile или AtomicBoolean; если нужны атомарные операции над несколькими полями — synchronized.
boolean stop = false; \text{boolean stop = false;} boolean stop = false; // поток 1
while(!stop) { ... } \text{while(!stop) \{ ... \}} while(!stop) { ... } // поток 2
stop = true; \text{stop = true;} stop = true;
может привести к тому, что поток 1 никогда не увидит изменение и застрянет в цикле.
Почему volatile или synchronized это меняют
- volatile:
- Объявление volatile boolean stop; \text{volatile boolean stop;} volatile boolean stop; запрещает кэширование значения переменной в регистре/локальном кешe: чтение всегда берётся из основной памяти.
- Гарантирует семантику happens-before: запись stop = true \text{stop = true} stop = true happens-before последующего чтения stop \text{stop} stop в другом потоке — значит изменение гарантированно увидят.
- Также запрещает определённые перестановки компилятором/процессором относительно этой переменной.
- Применение: простой флаг для уведомления — подходит.
- synchronized:
- Вход и выход из монитора (synchronized) создают happens-before: выход (unlock) одного потока happens-before вход (lock) другого потока.
- Если оба потока читают/пишут под тем же lock, то видимость гарантирована (и дополнительно обеспечена взаимная исключённость).
- Более тяжелый по стоимости, но нужен, если помимо видимости требуется атомарность/несколько связанных полей.
Примеры (упрощённо)
1) Проблемный (нет гарантии):
class Flag { boolean stop = false; }
2) volatile (решает проблему):
class Flag { volatile boolean stop = false; }
3) synchronized (решает через блоки):
class Flag { boolean stop = false; Object lock = new Object(); }
writer:
synchronized(lock) { stop = true; }
reader:
while (true) {
synchronized(lock) {
if (stop) break;
}
// ...
}
Рекомендация: для простого флага используйте volatile или AtomicBoolean; если нужны атомарные операции над несколькими полями — synchronized.