Все три механизма применяются при работе с общими данными из нескольких потоков, но дают разные гарантии. Неправильный выбор может привести к багам, которые сложно воспроизвести.
Коротко:
•
volatile — видимость изменений и гарантии порядка.•
Atomic — атомарные операции над отдельной переменной.•
synchronized — взаимное исключение и видимость изменений.volatile: когда важна видимость
Подходит, например, для флага остановки:
private volatile boolean running = true;
// Один поток:
running = false;
// Другой поток:
while (running) {
// Работа
}
Изменение флага будет видно при последующих чтениях из другого потока. Но
volatile не делает составные операции атомарными:volatile int count;
count++;
Инкремент состоит из чтения, увеличения и записи. Между этими шагами другой поток может изменить значение — и одно из обновлений потеряется.
Atomic: когда нужна атомарная операция
В Java есть
AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference и другие классы:private final AtomicInteger counter =
new AtomicInteger();
counter.incrementAndGet();
Здесь увеличение счётчика выполняется атомарно, без явного блока
synchronized. Типичные задачи: счётчики, последовательные номера, условное обновление флагов и ссылок. При этом несколько отдельных вызовов Atomic не становятся одной атомарной операцией автоматически.
synchronized: когда нужно защитить несколько действий
public synchronized void updateState() {
checkState();
changeState();
updateCounter();
}Только один поток может выполнять код, защищённый одним и тем же монитором, в конкретный момент. Для согласованности все обращения к защищаемому состоянию должны соблюдать ту же схему синхронизации.
synchronized обеспечивает взаимное исключение и видимость изменений. При этом он не откатывает уже выполненные действия при исключении и не заменяет транзакцию базы данных.Конкуренция за блокировку может приводить к ожиданию, а неправильный порядок захвата нескольких блокировок — к deadlock.
Вопрос с собеседования: достаточно ли volatile для count++? Нет. При конкурентном увеличении счётчика нужны
AtomicInteger.incrementAndGet() или защита всей операции одной блокировкой.Atomic всегда быстрее synchronized? Тоже нет. При высокой конкуренции операции на основе CAS могут многократно повторять попытки обновления. Выбор зависит от нагрузки, размера критической секции и количества связанных переменных.
Выбирайте по нужной гарантии:
→
volatile — другие потоки должны видеть изменение.→
Atomic — нужна атомарная операция над переменной.→
synchronized — нужно согласованно выполнить несколько действий с общим состоянием👉 Java Portal
