musthave
Процесс — это запущенная программа, которой операционная система выделяет собственное изолированное адресное пространство и системные ресурсы. Поток — это легковесная единица выполнения внутри процесса, которая делит с другими потоками его общую память, но выполняет инструкции независимо.
› Базовые абстракции и распределение памяти
- Thread: Класс, представляющий поток выполнения. При вызове
new Thread()в Heap (Куче) создается обычный Java-объект. Системный поток на этом этапе не существует. При вызове методаstart()JVM обращается к ОС, которая создает нативный поток и выделяет под него Стек (Stack) в нативной памяти (вне Java Heap). - Runnable: Интерфейс задачи с методом
run(). Не возвращает результат и не выбрасывает проверяемые (checked) исключения. - Callable<V>: Интерфейс задачи с методом
call(). Возвращает результат типаVи может выбрасывать проверяемые исключения. - Future<V>: Объект-дескриптор, возвращаемый при отправке задачи в пул. Позволяет проверить статус (
isDone()), отменить задачу (cancel()) и получить результат блокирующим методомget().
› Состояния потоков (Thread States)
- NEW (Новый): Поток создан (
new Thread()), но работа еще не началась. Планировщик ОС о нем не знает. Переход: создание объекта. - RUNNABLE (Выполняемый): Поток готов к работе или уже выполняется. Объединяет состояния ожидания процессора (Ready) и фактического выполнения (Running). Переход: вызов
start(). - BLOCKED (Заблокирован): Поток ожидает освобождения монитора для входа в
synchronizedблок/метод. Ресурсы CPU не потребляются. - WAITING (Ожидание): Поток добровольно перешел в режим бесконечного ожидания сигнала от другого потока. Переход:
Object.wait(),Thread.join(),LockSupport.park(). Выход:notify(),notifyAll(),unpark(). - TIMED_WAITING (Ожидание с таймером): Ожидание с заданным таймаутом. Переход:
Thread.sleep(ms),wait(ms),join(ms). - TERMINATED (Завершен): Поток завершил выполнение (успешно или из-за исключения). Повторный запуск невозможен.
› Синхронизация и Java Memory Model (JMM)
Базовые понятия синхронизации
- Мьютекс (Mutex): Низкоуровневый механизм взаимного исключения. Гарантирует, что ресурсом единовременно владеет только один поток.
- Семафор (Semaphore): Механизм синхронизации, ограничивающий количество потоков, которые могут одновременно получить доступ к ресурсу (допускает потоков).
- Монитор (Monitor) — Определение для собеседования: Это высокоуровневый механизм синхронизации, обеспечивающий взаимное исключение (только один поток выполняет критическую секцию) и координацию потоков (через методы
wait/notify). В Java каждый объект имеет ассоциированный с ним неявный монитор, управление которым происходит автоматически при использовании ключевого словаsynchronized.
Проблемы многопоточности
- Visibility problem (Проблема видимости): Ситуация, когда один поток изменил значение переменной, а другой поток этого не видит из-за кэширования на уровне регистров/L1-L2 кэшей CPU или оптимизаций компилятора.
- Race condition (Состояние гонки): Логическая ошибка проектирования, при которой результат выполнения программы зависит от непредсказуемого порядка планирования потоков ОС.
- Data race (Гонка данных): Ошибка на уровне памяти. Возникает, когда два потока одновременно обращаются к одной ячейке памяти, один из них выполняет запись, и между этими операциями не установлено отношение happens-before.
- Deadlock (Взаимная блокировка): Ситуация, при которой два или более потока бесконечно ожидают освобождения ресурсов, перекрестно захваченных друг другом.
Инструменты JMM
- Happens-before: Отношение в JMM, гарантирующее, что если действие A happens-before действия B, то результаты действия A будут гарантированно видимы в B, и инструкции не будут переупорядочены компилятором.
- volatile: Ключевое слово, гарантирующее видимость изменений. Чтение и запись переменной происходят напрямую в RAM (Main Memory), минуя кэши CPU. Не гарантирует атомарность составных операций (например, инкремента).
- synchronized: Гарантирует и видимость (устанавливает happens-before при захвате/освобождении монитора), и взаимное исключение (атомарность критической секции).
- synchronized метод: Синхронизируется на мониторе
this(илиClass-объекта, если метод статический). - synchronized блок: Позволяет явно указать объект-монитор и сузить область блокировки.
- synchronized метод: Синхронизируется на мониторе
› Продвинутая синхронизация (java.util.concurrent)
- ReentrantLock: Реализация интерфейса
Lock. Обеспечивает взаимное исключение и реентерабельность (повторный захват замка тем же потоком). Отличия отsynchronized: поддержка честности (fairness), возможность неблокирующего захвата (tryLock), возможность прерывания ожидания (lockInterruptibly). - CAS (Compare-And-Swap): Низкоуровневая атомарная аппаратная инструкция. Сравнивает текущее значение в памяти с ожидаемым, и если они равны, записывает новое значение.
- Atomic классы: Классы (
AtomicInteger,AtomicReferenceи др.), обеспечивающие потокобезопасные операции над одиночными переменными без использования блокировок. Базируются на алгоритмах non-blocking synchronization и инструкции CAS.
› Управление потоками (Thread Pools)
- ThreadPool (Пул потоков): Механизм управления группой переиспользуемых потоков. Снижает накладные расходы ОС на создание/уничтожение потоков.
- ExecutorService: Высокоуровневый API для управления пулами. Принимает задачи (
submit), управляет очередью, возвращаетFutureи обеспечивает корректное завершение (shutdown). - newFixedThreadPool(n): Фиксированное число потоков. Задачи сверх лимита помещаются в безграничную очередь (
LinkedBlockingQueue). - newCachedThreadPool(): Создает потоки по мере необходимости. Простаивающие потоки уничтожаются через 60 секунд. Очередь отсутствует (
SynchronousQueue). - ForkJoinPool: Пул, реализующий алгоритм Work-Stealing (кража работы). Простаивающие потоки забирают задачи из конца очередей занятых потоков. Оптимизирован для рекурсивных задач (“разделяй и властвуй”).
- Virtual Threads (Java 21): Легковесные потоки, управляемые JVM (модель M:N).
- Преимущества: Минимальное потребление памяти, блокировка виртуального потока не блокирует платформенный поток (ОС).
- Когда использовать: Для IO-bound задач (сеть, БД, файлы). Не подходят для CPU-bound задач.
› Асинхронное программирование (CompletableFuture)
CompletableFuture (CF) — расширенная реализация Future, поддерживающая функциональный стиль, построение цепочек (composition) и неблокирующую обработку результатов и ошибок.
Отличия от Future:
- Неблокирующее чтение (через колбэки
thenApply,thenAccept). - Возможность ручного завершения (
complete()). - Композиция задач (объединение, ожидание нескольких).
- Встроенная обработка исключений.
Блокирующий vs Неблокирующий код
- Блокирующий код: Останавливает выполнение потока до наступления события (I/O, лок). Поток простаивает, но удерживает ресурсы ОС и память стека.
- Почему избегать в CF: Блокировки внутри колбэков CF исчерпывают потоки
ForkJoinPool.commonPool(), что приводит к деградации производительности всего приложения (Thread Starvation).
Основные методы CompletableFuture
| Группа | Метод | Сигнатура колбэка | Результат | Описание |
|---|---|---|---|---|
| Инициализация | supplyAsync | Supplier\<U\> | CF\<U\> | Запуск задачи, возвращающей данные. |
runAsync | Runnable | CF\<Void\> | Запуск задачи без возврата данных. | |
| Трансформация | thenApply | Function\<T, U\> | CF\<U\> | Преобразует результат (аналог map). |
thenCompose | Function\<T, CF\<U\>\> | CF\<U\> | ”Сплющивает” вложенные Future (аналог flatMap). | |
| Потребление | thenAccept | Consumer\<T\> | CF\<Void\> | Потребляет результат, ничего не возвращая. |
| Комбинация | thenCombine | BiFunction\<T, U, V\> | CF\<V\> | Ждет оба Future и объединяет результаты. |
| Группировка | allOf | CF[] | CF\<Void\> | Ждет завершения всех переданных задач. |
anyOf | CF[] | CF\<Object\> | Ждет завершения любой первой задачи. | |
| Ошибки | exceptionally | Function\<Throwable, T\> | CF\<T\> | Возвращает дефолтное значение при ошибке. |
handle | BiFunction\<T, Th, U\> | CF\<U\> | Универсальная обработка (результат + ошибка). | |
whenComplete | BiConsumer\<T, Th\> | CF\<T\> | Побочный эффект (логгирование), не меняет результат. |
Важные нюансы CF:
thenApply()vsthenApplyAsync():thenApplyвыполняется синхронно в потоке, завершившем предыдущую задачу (или в вызывающем потоке).thenApplyAsyncотправляет задачу на параллельное выполнение в пул потоков.- Пул по умолчанию: Для всех
*Asyncметодов без явного указания пула используетсяForkJoinPool.commonPool(). - Кастомный Executor: Передается вторым аргументом в
*Asyncметоды (например,supplyAsync(supplier, customExecutor)). join()vsget(): Оба метода блокируют поток до получения результата. Разница в сигнатуре:get()выбрасывает проверяемые исключения (InterruptedException,ExecutionException), аjoin()оборачивает их в непроверяемоеCompletionException.