Найти в Дзене
Записки о Java

Happens-Before в Java: как JVM гарантирует видимость изменений между потоками

Стек: Java 11, многопоточность, Java Memory Model (JMM)
Цель: понять, что такое happens-before, как он обеспечивает видимость и упорядочение, и почему volatile и блокировки “работают”. В многопоточной Java-программе изменения одного потока могут быть невидимы другому из-за: Без гарантий видимости и упорядочения вы можете получить: 💡 Java Memory Model (JMM) решает эту проблему через правила happens-before — формальный контракт между потоками. Happens-before — это отношение “происходит до” между двумя действиями в программе.
Если действие A happens-before действия B, то: Изменения, сделанные в A, гарантированно видны в B. Это — основа безопасности в многопоточных программах без synchronized. Java Language Specification (JLS) определяет несколько встроенных правил happens-before: Внутри одного потока каждое действие happens-before всех последующих действий (согласно порядку в коде). // В одном потоке: int x = 1; // A int y = 2; // B // A happens-before B Разблокировка монитора (unlo
Оглавление
Рисунок: Happens - Before в Java
Рисунок: Happens - Before в Java

Стек: Java 11, многопоточность, Java Memory Model (JMM)
Цель: понять, что такое happens-before, как он обеспечивает видимость и упорядочение, и почему volatile и блокировки “работают”.

Почему вообще нужен happens-before?

В многопоточной Java-программе изменения одного потока могут быть невидимы другому из-за:

  • Кэшей CPU (каждый ядро имеет свой кэш),
  • Оптимизаций компилятора (перестановка инструкций),
  • Оптимизаций процессора (out-of-order execution).

Без гарантий видимости и упорядочения вы можете получить:

  • Старые значения переменных,
  • Частично инициализированные объекты,
  • Нелогичное поведение программы.
💡 Java Memory Model (JMM) решает эту проблему через правила happens-before — формальный контракт между потоками.

📜 Что такое happens-before?

Happens-before — это отношение “происходит до” между двумя действиями в программе.
Если
действие A happens-before действия B, то:

Изменения, сделанные в A, гарантированно видны в B.

Это — основа безопасности в многопоточных программах без synchronized.

Ключевые правила happens-before (из JLS)

Java Language Specification (JLS) определяет несколько встроенных правил happens-before:

1. Program Order Rule

Внутри одного потока каждое действие happens-before всех последующих действий (согласно порядку в коде).

// В одном потоке:

int x = 1; // A

int y = 2; // B

// A happens-before B

2. Monitor Lock Rule

Разблокировка монитора (unlock) happens-before последующей блокировки (lock) этого же монитора.
🔑 Это — основа работы synchronized!

Пример:

class Counter {

private int count = 0;

public synchronized void increment() {

count++; // Изменения видны всем последующим synchronized-вызовам

}

public synchronized int get() {

return count;

}

}

Почему это безопасно?

  • Поток A вызывает increment() → разблокирует монитор при выходе.
  • Поток B вызывает get() → блокирует монитор.
  • По правилу: unlock(A) happens-before lock(B)изменения A видны в B.

3. Volatile Variable Rule

Запись в volatile переменную (write) happens-before последующего чтения (read) этой же переменной.
🔑 Это — основа работы volatile!

Пример:

Рисунок: пример volatile variable rule
Рисунок: пример volatile variable rule

Почему data виден как 42?

  • data = 42 happens-before ready = true (Program Order в потоке writer),
  • ready = true (volatile write) happens-before if (ready) (volatile read),
  • Следовательно: data = 42 happens-before System.out.println(data) → значение видно.
⚠️ Без volatile — нет гарантии, что reader увидит data = 42!

4. Другие правила (кратко)

  • Thread Start Rule: Thread.start() happens-before любых действий в новом потоке.
  • Thread Join Rule: Все действия в потоке happen-before thread.join().
  • Transitivity: Если A hb B и B hb C, то A hb C.

⚠️ Распространённые заблуждения

Миф

Реальность

«volatile делает операцию атомарной»

Нет! volatile гарантирует только видимость и упорядочение, но не атомарность. volatile int x; x++ — не атомарно!

«Если переменная final — она всегда видна»

Только если объект правильно опубликован (например, через synchronized или volatile).

«Кэши CPU — главная проблема»

Проблема — в JMM, которая абстрагируется от железа. Даже на одном ядре компилятор может всё сломать!

Практический пример: публикация объекта

Как безопасно опубликовать объект из одного потока в другой?

Неправильно (без happens-before):

Рисунок: пример неправильно опубликованного объекта
Рисунок: пример неправильно опубликованного объекта

Правильно (через volatile):

Рисунок: пример рекомендуемой публикации объекта
Рисунок: пример рекомендуемой публикации объекта

🔥 Рекомендация:
Если операция
нельзя выразить одним volatile чтением/записью — используйте synchronized или java.util.concurrent.

Заключение

  • Happens-before — это не магия, а формальный контракт JMM.
  • synchronized работает благодаря unlock → lock.
  • volatile работает благодаря write → read.
  • Без happens-before — нет гарантий видимости.
🔑 Главное:
Если между потоками нет happens-before отношения — поведение непредсказуемо.