В чем разница между synchronized и volatile в Java?

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

Ответ

Оба ключевых слова связаны с многопоточностью, но решают разные проблемы и не являются взаимозаменяемыми.

volatile (Только видимость)

  • Гарантирует видимость изменений переменной между потоками.
  • Не гарантирует атомарность составных операций (например, count++, который состоит из чтения, изменения и записи).
  • Механизм: Запрещает процессору и JVM кэшировать значение переменной в регистрах потока. Все чтения и записи идут напрямую в основную память (main memory).
  • Использование: Для простых флагов или переменных состояния, запись в которые выполняется одним атомарным действием.
public class TaskController {
    private volatile boolean isRunning = true; // Флаг виден всем потокам сразу

    public void stop() {
        isRunning = false; // Изменение сразу станет видно потоку worker
    }

    public void run() {
        while (isRunning) { // Чтение всегда актуального значения из памяти
            // выполнять работу
        }
    }
}

synchronized (Видимость + Атомарность + Взаимное исключение)

  • Гарантирует взаимное исключение (mutual exclusion): только один поток может выполнять синхронизированный блок/метод для данного объекта-монитора.
  • Гарантирует атомарность: Весь блок кода выполняется как одна неделимая операция.
  • Гарантирует видимость: При выходе из synchronized-блока все изменения переменных, сделанные в нем, становятся видимыми другим потокам (запись в основную память).
  • Использование: Для защиты критических секций, где состояние объекта может быть изменено несколькими потоками.
public class Counter {
    private int count = 0;

    // Без synchronized операция count++ не атомарна
    public synchronized void increment() {
        count++; // Чтение-изменение-запись защищены
    }

    public synchronized int getCount() {
        return count; // Чтение также защищено для согласованности
    }
}

Сравнительная таблица

Критерий volatile synchronized
Применимость Только к переменным К блокам кода и методам
Атомарность Нет (только для одиночных операций записи) Да
Видимость Да (чтение/запись в основную память) Да (вход/выход из блока)
Взаимное исключение Нет Да
Блокировка потоков Нет Да (может привести к ожиданию)
Производительность Высокая Ниже из-за накладных расходов на блокировку

Правило выбора:

  • Используйте volatile для простых флагов или переменных, записываемых одним потоком и читаемых многими, где атомарность не требуется.
  • Используйте synchronized (или более современные java.util.concurrent примитивы) для операций, требующих атомарности или изменения нескольких связанных переменных.