Поток как единица выполнения
Поток — независимая последовательность выполнения внутри процесса; все потоки делят кучу, но у каждого свой стек. Задачу описывают через Runnable (или Callable, если нужен результат), а Thread её выполняет.
Платформенный Thread — это тонкая обёртка над потоком ОС, и он дорог: ~1 МБ стека и переключения контекста через ядро. Поэтому потоки не создают на каждый запрос, а переиспользуют через пулы (отдельный урок).
Частая ошибка — вызвать run() вместо start(): run() просто выполнит код в текущем потоке, а новый поток создаёт именно start().
Монитор: основа синхронизации
Раз потоки делят память, им нужен механизм согласованного доступа — в Java это монитор (его ещё называют intrinsic lock). С каждым объектом неявно связан монитор, и в любой момент его может удерживать только один поток, остальные ждут. Это базовый кирпич взаимного исключения, на котором держится вся синхронизация языка.
Поток «входит» в монитор и «выходит» из него: вход даёт эксклюзивный доступ к защищаемой секции, а выход к тому же делает сделанные изменения видимыми следующему потоку (по правилу happens-before). Мониторы реентерабельны — поток, уже владеющий монитором, может войти в него повторно без самоблокировки.
Запомните это определение сразу: монитор будет всплывать всю тему — на нём построен synchronized, через него описаны состояния BLOCKED/WAITING ниже и методы wait/notify. Подробный разбор — в уроке про synchronized.
Состояния жизненного цикла
Поток проходит состояния (Thread.State): NEW (создан, не запущен), RUNNABLE (выполняется или готов), BLOCKED (ждёт входа в монитор под synchronized), WAITING (бессрочно ждёт — wait/join/park), TIMED_WAITING (ждёт с таймаутом — sleep, wait(t)), TERMINATED (завершён).
Различие BLOCKED и WAITING важно: BLOCKED — это конкуренция за монитор, WAITING — добровольное ожидание условия. По дампу потоков именно эти состояния показывают, где система «застряла».

Прерывание и кооперативная отмена
В Java нет принудительной остановки потока (Thread.stop() признан небезопасным — он мог оставить инварианты в полусломанном состоянии — и с Java 20 всегда бросает UnsupportedOperationException). Отмена кооперативна: interrupt() лишь выставляет флаг, а поток сам обязан его проверять и завершаться.
Блокирующие методы (sleep, wait, join, блокирующий IO в каналах) реагируют на флаг, бросая InterruptedException. Правило: не глотать его молча — либо пробросить, либо восстановить флаг через Thread.currentThread().interrupt(), иначе отмена «теряется».
while (!Thread.currentThread().isInterrupted()) {
try { queue.take(); }
catch (InterruptedException e) {
Thread.currentThread().interrupt(); // восстановить флаг
break; // и корректно выйти
}
}
Daemon-потоки и завершение
Потоки бывают user и daemon. JVM завершается, когда не остаётся ни одного живого user-потока; daemon-потоки (фоновые: GC-хелперы, таймеры) при этом просто убиваются. Поэтому в daemon нельзя держать критичную работу, которую нельзя оборвать на полуслове.
join() позволяет дождаться завершения другого потока — между его завершением и возвратом из join есть happens-before, поэтому результаты работы потока видны после join.
Как отвечать на собеседовании
Отличайте start()/run(), назовите состояния и разницу BLOCKED/WAITING (для чтения thread dump), и грамотно объясните кооперативную отмену: interrupt() — это флаг, InterruptedException нельзя глотать (восстановить флаг), а stop() удалён. Упомяните дороговизну платформенных потоков как мотивацию пулов и виртуальных потоков.