офлайн
офлайн
ГлавнаяМногопоточность Java.md
---
tags: [java, concurrency]
created: 2026-11-05
updated: 
---

Многопоточность Java

Javajavaconcurrency5 мин
Содержание

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)

  1. NEW (Новый): Поток создан (new Thread()), но работа еще не началась. Планировщик ОС о нем не знает. Переход: создание объекта.
  2. RUNNABLE (Выполняемый): Поток готов к работе или уже выполняется. Объединяет состояния ожидания процессора (Ready) и фактического выполнения (Running). Переход: вызов start().
  3. BLOCKED (Заблокирован): Поток ожидает освобождения монитора для входа в synchronized блок/метод. Ресурсы CPU не потребляются.
  4. WAITING (Ожидание): Поток добровольно перешел в режим бесконечного ожидания сигнала от другого потока. Переход: Object.wait(), Thread.join(), LockSupport.park(). Выход: notify(), notifyAll(), unpark().
  5. TIMED_WAITING (Ожидание с таймером): Ожидание с заданным таймаутом. Переход: Thread.sleep(ms), wait(ms), join(ms).
  6. 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 блок: Позволяет явно указать объект-монитор и сузить область блокировки.

› Продвинутая синхронизация (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:

  1. Неблокирующее чтение (через колбэки thenApply, thenAccept).
  2. Возможность ручного завершения (complete()).
  3. Композиция задач (объединение, ожидание нескольких).
  4. Встроенная обработка исключений.

Блокирующий vs Неблокирующий код

  • Блокирующий код: Останавливает выполнение потока до наступления события (I/O, лок). Поток простаивает, но удерживает ресурсы ОС и память стека.
  • Почему избегать в CF: Блокировки внутри колбэков CF исчерпывают потоки ForkJoinPool.commonPool(), что приводит к деградации производительности всего приложения (Thread Starvation).

Основные методы CompletableFuture

ГруппаМетодСигнатура колбэкаРезультатОписание
ИнициализацияsupplyAsyncSupplier\<U\>CF\<U\>Запуск задачи, возвращающей данные.
runAsyncRunnableCF\<Void\>Запуск задачи без возврата данных.
ТрансформацияthenApplyFunction\<T, U\>CF\<U\>Преобразует результат (аналог map).
thenComposeFunction\<T, CF\<U\>\>CF\<U\>”Сплющивает” вложенные Future (аналог flatMap).
ПотреблениеthenAcceptConsumer\<T\>CF\<Void\>Потребляет результат, ничего не возвращая.
КомбинацияthenCombineBiFunction\<T, U, V\>CF\<V\>Ждет оба Future и объединяет результаты.
ГруппировкаallOfCF[]CF\<Void\>Ждет завершения всех переданных задач.
anyOfCF[]CF\<Object\>Ждет завершения любой первой задачи.
ОшибкиexceptionallyFunction\<Throwable, T\>CF\<T\>Возвращает дефолтное значение при ошибке.
handleBiFunction\<T, Th, U\>CF\<U\>Универсальная обработка (результат + ошибка).
whenCompleteBiConsumer\<T, Th\>CF\<T\>Побочный эффект (логгирование), не меняет результат.

Важные нюансы CF:

  • thenApply() vs thenApplyAsync(): thenApply выполняется синхронно в потоке, завершившем предыдущую задачу (или в вызывающем потоке). thenApplyAsync отправляет задачу на параллельное выполнение в пул потоков.
  • Пул по умолчанию: Для всех *Async методов без явного указания пула используется ForkJoinPool.commonPool().
  • Кастомный Executor: Передается вторым аргументом в *Async методы (например, supplyAsync(supplier, customExecutor)).
  • join() vs get(): Оба метода блокируют поток до получения результата. Разница в сигнатуре: get() выбрасывает проверяемые исключения (InterruptedException, ExecutionException), а join() оборачивает их в непроверяемое CompletionException.

Ссылаются сюда 1

main48 заметок939 слов5 мин чтения