Ответ
Нет, без 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-операциями. Бери и пользуйся.
Короче, без этих штук в многопоточке — это как играть в жмурки в кромешной тьме. Один кричит «я здесь!», а все остальные его нихуя не слышат.