Обеспечивает ли модификатор volatile потокобезопасный инкремент счетчика в Java?

«Обеспечивает ли модификатор volatile потокобезопасный инкремент счетчика в Java?» — вопрос из категории Java Core, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Нет. Ключевое слово volatile не гарантирует атомарность составных операций, таких как инкремент (i++).

  • Что дает volatile: Гарантирует видимость (visibility) изменений переменной между потоками. Когда один поток записывает значение в volatile-переменную, это изменение немедленно становится видимым для всех других потоков.
  • Почему инкремент небезопасен: Операция counter++ — это не одна, а три операции (read-modify-write):
    1. Чтение текущего значения.
    2. Увеличение значения на 1.
    3. Запись нового значения. Два потока могут прочитать одно и то же старое значение, инкрементировать его и записать, в результате чего одно увеличение будет потеряно.

Потокобезопасные альтернативы:

  1. Классы из пакета java.util.concurrent.atomic (наиболее эффективно):

    import java.util.concurrent.atomic.AtomicInteger;
    
    AtomicInteger atomicCounter = new AtomicInteger(0);
    atomicCounter.incrementAndGet(); // Атомарный инкремент
  2. Синхронизация (synchronized):

    public class Counter {
        private int counter = 0;
    
        public synchronized void increment() {
            counter++;
        }
    }
  3. Использование ReentrantLock:

    import java.util.concurrent.locks.ReentrantLock;
    
    public class Counter {
        private int counter = 0;
        private final ReentrantLock lock = new ReentrantLock();
    
        public void increment() {
            lock.lock();
            try {
                counter++;
            } finally {
                lock.unlock();
            }
        }
    }

    volatile идеально подходит для флагов или переменных, где операция записи является простым присваиванием (flag = true), а не зависит от предыдущего значения.