Happens-before

Happens-before — отношение порядка в Java Memory Model. Если действие A happens-before B, эффекты A должны быть видимы B, а допустимое исполнение согласуется с этим порядком. Это логическая гарантия, а не обязательно разница во времени на физических часах.

Основные правила

AHappens-before B
действие в потокеследующее действие этого же потока
выход из synchronizedследующий вход в тот же monitor
запись в volatileпоследующее чтение этого записанного значения
thread.start()действия запущенного потока
действия потокауспешный возврат другого потока из join()
инициализация классапервое активное использование класса

Отношение транзитивно: если A предшествует B, а B предшествует C, то A предшествует C.

int payload = 0;
Thread producer = new Thread(() -> payload = 42);
producer.start();
producer.join();
System.out.println(payload); // join гарантирует видимость 42

Executor и Future

Для executor действует точное правило: действия потока до передачи Runnable или Callable happens-before действия задачи. Действия задачи, в свою очередь, happen-before получением результата через Future.get().

List<String> input = List.of("a", "bb");
Future<Integer> future = pool.submit(
        () -> input.stream().mapToInt(String::length).sum());
int total = future.get(); // видит результат задачи

Сам факт, что две независимые задачи выполняются одним executor, не создаёт между ними happens-before. Нужна явная граница: Future.get, lock, volatile, latch, очередь или другой синхронизатор.

Что правило не гарантирует

  • A и B не обязаны выполняться на разных ядрах или в разные моменты часов.
  • Happens-before не означает атомарность составной операции.
  • sleep() и yield() не публикуют состояние.
  • «До по исходному коду» между разными потоками недостаточно.

Вопросы для собеседования

  1. Что гарантирует happens-before? Видимость эффектов и допустимый порядок.
  2. Создаёт ли start связь? Да, действия до start видимы новому потоку.
  3. Что гарантирует join? Действия завершившегося потока видимы после join.
  4. Есть ли связь между двумя задачами пула автоматически? Нет.
  5. Что даёт Future.get? Видимость действий завершённой задачи и её результат.
  6. Создаёт ли Thread.sleep happens-before? Нет.
  7. Почему важна транзитивность? Она переносит видимость через цепочку синхронизационных границ.