Урок по треку Java 8 мин

Потоки и Java Memory Model

Thread, synchronized, volatile, happens-before, wait/notify, дедлоки и гонки.

Многопоточность спрашивают на любом бэкенд-собесе по Java. Junior должен знать Thread, synchronized и гонки, middle volatile и happens-before, senior объяснить, почему double-checked locking без volatile сломан.

Поток

Thread t = new Thread(() -> System.out.println("hi"));
t.start();   // новый поток выполнит run()
t.join();    // ждём завершения

run() вместо start() выполнит код в текущем потоке. Повторный start() бросит IllegalThreadStateException. Состояния потока: NEW, RUNNABLE, BLOCKED (ждёт монитор), WAITING и TIMED_WAITING (join, wait, sleep, park), TERMINATED.

Исключение в потоке завершает только этот поток, main продолжит работу. Поймать его можно через UncaughtExceptionHandler.

В прикладном коде потоки руками почти не создают: есть пулы (ExecutorService) и виртуальные потоки, о них в следующем модуле.

Гонка и атомарность

private int count = 0;
void inc() { count++; }   // не атомарно: прочитать, прибавить, записать

Два потока, по 1000 вызовов каждый, дадут результат меньше 2000: обновления теряются. Это race condition. Лечится synchronized, AtomicInteger или LongAdder.

synchronized

synchronized void inc() { count++; }          // монитор this
static synchronized void f() { }              // монитор Class
synchronized (lock) { ... }                   // явный объект

Даёт взаимное исключение и видимость: всё, что поток записал до выхода из блока, увидит следующий поток, вошедший в блок на том же мониторе. Монитор реентерабелен: поток может войти повторно. Синхронизация по разным объектам ничего не защищает.

volatile и видимость

Без синхронизации поток может не увидеть запись другого потока: значение закешировано в регистре, или компилятор и процессор переставили инструкции.

private volatile boolean running = true;
void stop() { running = false; }          // другой поток
void loop() { while (running) { work(); } }

volatile гарантирует видимость и запрещает опасные перестановки вокруг переменной, но не атомарность: volatile int count; count++ всё ещё гонка.

Happens-before

Модель памяти Java (JMM) описывает, когда запись одного потока гарантированно видна другому. Основные правила happens-before:

  • внутри одного потока по порядку программы;
  • отпускание монитора happens-before последующего захвата того же монитора;
  • запись в volatile happens-before последующего чтения этой переменной;
  • t.start() happens-before любого действия в потоке t;
  • все действия потока happens-before успешного возврата из t.join();
  • правило транзитивно.

Если два доступа к переменной из разных потоков не связаны happens-before и хотя бы один из них запись, это data race, и результат не определён.

Безопасная публикация и DCL

private static volatile Singleton instance;
static Singleton get() {
    Singleton r = instance;
    if (r == null) {
        synchronized (Singleton.class) {
            r = instance;
            if (r == null) instance = r = new Singleton();
        }
    }
    return r;
}

Без volatile другой поток может увидеть ссылку на объект раньше, чем записи его полей из конструктора. Проще: holder-класс (private static class Holder { static final Singleton I = new Singleton(); }) или enum, инициализация класса потокобезопасна сама. Поля final, присвоенные в конструкторе, видны всем потокам после публикации, если this не утёк из конструктора.

wait/notify и дедлок

wait() отпускает монитор и ждёт notify(). Вызывать только внутри synchronized по тому же объекту и всегда в цикле while (!condition) wait(); из-за ложных пробуждений. В новом коде вместо них берут java.util.concurrent.

Дедлок: поток 1 держит A и ждёт B, поток 2 держит B и ждёт A. Лечится единым порядком захвата блокировок, tryLock с таймаутом, уменьшением областей блокировки. Найти можно по jstack: он пишет Found one Java-level deadlock.

Что спросят на собесе

  • Чем start() отличается от run()? Какие состояния у потока?
  • Что такое race condition, как его исправить?
  • Что гарантирует volatile и чего не гарантирует?
  • Что такое happens-before? Приведите правила.
  • Почему double-checked locking без volatile сломан?
  • Что такое дедлок и как его избежать?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.