В чем разница между synchronized и volatile в 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 примитивы) для операций, требующих атомарности или изменения нескольких связанных переменных.

Ответ 18+ 🔞

Давай разберём эту дичь, а то народ путает как слепой кот в сортире. volatile и synchronized — это как молоток и отвёртка: оба в ящике с инструментами, но ты же не будешь гвозди отвёрткой забивать, верно? Хотя некоторые пытаются, блядь.

volatile — это про видимость, ёпта! Представь, у тебя в кеше процессора лежит переменная, и каждый поток тащит свою копию, как собака кость. И один поток её поменял, а остальные об этом нихуя не знают — сидят, старые значения жуют. Так вот volatile — это такой крик на всю деревню: «Эй, мудаки, смотрите в основную память!». Он запрещает кеширование. Записал значение — оно сразу для всех видно. Но, внимание, хуйня в том, что атомарности тут нет! Операция i++ для volatile-переменной — это всё равно три шага: прочитать, увеличить, записать. И если два потока в это время впендюрятся, будет classic race condition, пиздец и слёзы.

public class TaskController {
    private volatile boolean isRunning = true; // Флаг. Один поток пишет, другие читают.

    public void stop() {
        isRunning = false; // Написал — и всем сразу видно. Идеально.
    }

    public void run() {
        while (isRunning) { // Читает всегда свежее из памяти, а не свою устаревшую хуйню.
            // делаем дела
        }
    }
}

synchronized — это тяжёлая артиллерия, блядь. Тут уже не просто видимость, а полный контроль. Это как зайти в сортир и закрыться на ключ. Пока ты там делаешь свои дела, все остальные ждут у двери, терпя бесконечное говно. Гарантирует и видимость, и атомарность, и взаимное исключение (mutual exclusion, если по-пацански).

public class Counter {
    private int count = 0; // Даже не volatile, ибо synchronized своё дело сделает.

    public synchronized void increment() {
        count++; // Всё! Ни один другой поток сюда не сунется, пока этот не закончит.
    }

    public synchronized int getCount() {
        return count; // И читать тоже через synchronized, чтобы гарантированно свежее взять.
    }
}

Краткая памятка для тех, кто в танке:

Что сравниваем volatile synchronized
Суть «Смотрите в основную память!» «Не лезь, пока я не выйду!»
Атомарность Нихуя. count++ разъебут. Да. Всё внутри блока — единая операция.
Видимость Да. Запись сразу видна всем. Да. При выходе из блока всё сбрасывается в память.
Блокировка Нет. Потоки не останавливаются. Да. Остальные ждут, как лохи.
Производительность Быстро, почти без накладок. Медленнее, потому что управление блокировками.

Итог, ебать его в сраку:

  • Висит простой флажок boolean isDone? volatile в самый раз.
  • Нужно сделать i++, или изменить несколько переменных согласованно? synchronized (или посмотри в сторону AtomicInteger, это вообще отдельная песня).
  • Использовать volatile для атомарных операций — это как пытаться забить гвоздь тапком. Вроде и стучишь, но результат обычно пиздецовый.