В Java, если переменная не помечена как volatile, будут ли её изменения гарантированно видны другим потокам?

Ответ

Нет, без volatile (или других механизмов синхронизации) видимость изменений между потоками не гарантируется. JVM и процессор могут кэшировать значение переменной в регистрах или локальной памяти потока для оптимизации.

Почему это происходит: Без volatile, synchronized или атомарных классов (AtomicInteger и т.д.) нет happens-before отношения, обеспечивающего, что запись в одном потоке будет видна чтению в другом.

Пример проблемы:

public class VisibilityProblem {
    // Без volatile - изменение может быть не видно
    boolean flag = false;

    void writerThread() {
        flag = true; // Запись может остаться в кэше потока
    }

    void readerThread() {
        while (!flag) { // Может читать закэшированное старое значение (false)
            // Бесконечный цикл
        }
        System.out.println("Flag is now true");
    }
}

Решение:

  • Добавить модификатор volatile к переменной flag.
  • Использовать синхронизацию (synchronized блоки).
  • Использовать атомарные классы из java.util.concurrent.atomic.

Ответ 18+ 🔞

А, слушай, вот это реально тот самый случай, когда без божественной воли volatile твоя многопоточка превращается в чистый пиздец. Ну представь себе: один поток там старается, меняет какую-нибудь переменную, а другой — нихуя не видит, как будто он слепой кот, который в углу сидит и на стену смотрит.

Почему так, блядь, происходит? Да потому что JVM и процессор — они же не дураки, они оптимизируют всё, что можно. Зачем каждый раз лазить в общую память, если можно взять значение переменной, запихнуть его себе в локальный кэш потока (или, там, в регистр) и дальше уже работать с этой копией? А другой поток про эту копию нихуя не знает, он со своим значением работает. И получается, что один уже давно вывел flag = true, а второй всё ещё тупо крутится в цикле, читая своё старое, закэшированное false. Вот и вся магия, ёпта. Без volatile, synchronized или атомарных штук — нет никакого happens-before отношения, это как договориться через стенку кричать — может долететь, а может и нет.

Вот, смотри, классический пиздец в коде:

public class VisibilityProblem {
    // Без volatile — это как письмо в бутылке, брошенное в океан. Доплывёт? Хуй его знает.
    boolean flag = false;

    void writerThread() {
        flag = true; // Тыкнул кнопку "Изменить" в своём потоке. А сигнал, блядь, может и не уйти.
    }

    void readerThread() {
        while (!flag) { // А этот чел сидит и ждёт. Ждёт-ждёт. А флаг уже давно true, но он про это в рот его чих-пых!
            // И вот он, вечный цикл, товарищи. Бесконечная печаль.
        }
        System.out.println("Flag is now true"); // Этой строчки можно и не дождаться, вот в чём трагедия.
    }
}

Что делать, чтобы не сойти с ума? Да элементарно, Ватсон! Надо дать пинка под жопу этой системе видимости.

  • volatile — это как крикнуть на всю квартиру: «Эй, все, значение изменилось, берите свежее!». Гарантирует, что чтение увидит последнюю запись.
  • synchronized блоки — это уже как собрание с протоколом. Зашёл в блок — получил самое актуальное состояние, вышел — записал изменения для всех.
  • Атомарные классы (AtomicInteger и прочая братия) — они там внутри уже всё по-умному сделали, с теми же volatile и CAS-операциями. Бери и пользуйся.

Короче, без этих штук в многопоточке — это как играть в жмурки в кромешной тьме. Один кричит «я здесь!», а все остальные его нихуя не слышат.