synchronized, volatile, пулы потоков, CompletableFuture и Java Memory Model.
- Что гарантирует ключевое слово volatile для переменной? Middle
volatile даёт видимость (запись сразу видна другим потокам) и запрет переупорядочивания вокруг обращения, но не атомарность: count++ на volatile-поле всё равно теряет обновления.
- Чем volatile отличается от synchronized? Middle
volatile — видимость и порядок для одной переменной без блокировки. synchronized — видимость плюс взаимное исключение: блок целиком выполняется одним потоком, поэтому составные…
- Чем Thread, Runnable и Callable отличаются? Junior
Thread — сам поток выполнения. Runnable — задача без результата (void run()), Callable<V> — задача с результатом V call(), которая может бросить проверяемое исключение.
- В чём опасность ThreadLocal в пуле потоков? Middle
Поток в пуле переиспользуется: значение ThreadLocal, не очищенное после задачи, протекает в следующую задачу (чужой пользователь, транзакция) и удерживает память. Правило: try {…
- Что такое deadlock и как его предотвращать? Middle
Deadlock — циклическое ожидание: поток A держит замок 1 и ждёт замок 2, поток B держит 2 и ждёт 1. Предотвращают единым порядком захвата, tryLock с таймаутом и откатом, короткими…
- Чем различаются CountDownLatch, CyclicBarrier и Semaphore? Middle
CountDownLatch — одноразовое ожидание N событий: дошёл до нуля — открыт навсегда. CyclicBarrier — переиспользуемая точка встречи N потоков между раундами. Semaphore — ограничение…
- Что такое Fork/Join framework и work stealing? Senior
Fork/Join — рекурсивное деление задачи на подзадачи (fork) с последующим объединением результатов (join). Work stealing: у каждого рабочего потока своя двусторонняя очередь, а…
- Реализуй потокобезопасный счётчик Counter с методами increment() и get(). Middle
Проще всего — AtomicInteger: incrementAndGet() и get(), без блокировок. Альтернатива — synchronized на обоих методах (важно: и на чтении тоже, иначе нет видимости).
- Какое ограничение Future решает CompletableFuture? Middle
Future — дескриптор результата с единственным способом получить значение: блокирующий get(). CompletableFuture позволяет описать цепочку продолжений (thenApply, thenCompose…
- Чем thenCompose отличается от thenApply в CompletableFuture? Middle
thenApply — как map: функция значение → значение. thenCompose — как flatMap: функция значение → новый CompletableFuture, результат «разворачивается». Без thenCompose получилось бы…
- Почему опасно выполнять блокирующие операции в стадиях CompletableFuture без явного Executor? Senior
Async-стадии без Executor выполняются в ForkJoinPool.commonPool — он общий на всю JVM и размером примерно число ядер − 1. Блокирующие вызовы (HTTP, БД, get чужого future) занимают…
- Поток пытается войти в synchronized-блок, занятый другим потоком. В каком состоянии он находится? Middle
BLOCKED — ожидание входа в занятый монитор synchronized. Шесть состояний: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.
- Что верно про прерывание потока через interrupt()? Middle
interrupt() — просьба, а не приказ: выставляет флаг; блокирующие sleep, wait, join бросают InterruptedException и сбрасывают флаг. Поток проверяет флаг сам и завершается в…
- Что верно про daemon-потоки? Middle
JVM завершается, когда остались только daemon-потоки; они обрываются без выполнения finally. Флаг ставится до start(), иначе IllegalThreadStateException.
- Почему фабрики из класса Executors считают опасными в production-коде? Middle
Скрытые неограниченные ресурсы: newFixedThreadPool — неограниченная очередь (задачи копятся до OutOfMemoryError, наружу не идёт никакого отказа), newCachedThreadPool …
- Сервис в основном ходит по сети: на каждую задачу примерно 90% времени — ожидание ответа и 10% — вычисления. Как выбрать размер пула потоков? Middle
IO-bound: ядра × (1 + W/C), где W — ожидание, C — вычисления; при 90/10 — около 10 × ядер. CPU-bound — примерно по числу ядер. Это ориентир: реальный предел часто задаёт внешний…
- Задача, отправленная в пул через submit(), бросает RuntimeException, но в логах ничего нет. Почему? Middle
submit() прячет исключение в Future: оно появится только при get() в ExecutionException. Если результат никто не запрашивает — ошибка исчезает бесследно. execute() пропускает…
- Нужно ограничить число одновременных обращений к внешнему API десятью, не ограничивая при этом число рабочих потоков. Какой примитив подходит? Middle
Semaphore с десятью разрешениями: acquire перед вызовом, release в finally. Он ограничивает число одновременных доступов к ресурсу, не трогая число потоков.
- Можно ли переиспользовать CountDownLatch после того, как счётчик дошёл до нуля? Middle
Нет: дошедший до нуля CountDownLatch открыт навсегда, метода сброса нет. Для повторяющихся раундов — CyclicBarrier или Phaser; либо новый latch на каждый раунд.
- В коде вызывается semaphore.acquire(), затем работа с ресурсом, затем semaphore.release() в конце метода. Что здесь опасно? Middle
release не в finally: исключение навсегда съедает разрешение. С каждым сбоем параллелизм падает на единицу, пока система не встанет без единой записи в логе.
- Что верно про практическое применение виртуальных потоков? Middle
Не пулить (поток на задачу), применять для блокирующего ввода-вывода, помнить, что ThreadLocal на миллионах потоков дорог, и ограничивать доступ к внешним ресурсам семафором — пул…
- Что такое pinning виртуального потока и в каких случаях он всё ещё возможен в актуальных версиях Java? Senior
Pinning — виртуальный поток при блокировке не может отцепиться от несущего, и тот простаивает. До JDK 24 главная причина — блокировка внутри synchronized; JEP 491 (JDK 24) это…
- Что происходит с виртуальным потоком, когда он выполняет блокирующий сетевой вызов? Middle
Unmount: стек виртуального потока (continuation) сохраняется в куче, несущий поток освобождается для других задач. Когда данные пришли — mount обратно, возможно на другой несущий…
- Почему вызов wait() принято помещать в цикл while, а не в if? Middle
Пробуждение не гарантирует, что условие выполнено: возможны ложные пробуждения (spurious wakeup), а между notify и захватом монитора другой поток мог снова изменить состояние…
- На каких объектах не следует синхронизироваться? Middle
Не синхронизироваться на разделяемых или подменяемых объектах: строковых литералах (интернированы), Integer и других обёртках (кэш значений), публичном this, изменяемом поле…
- Что такое ABA-проблема в алгоритмах на CAS? Senior
CAS сравнивает только текущее значение, а не историю: A → B → A выглядит как «ничего не менялось». В lock-free структурах со ссылками это ломает данные (узел удалили и…
- Чем LongAdder выигрывает у AtomicLong при высокой конкуренции и в чём его ограничение? Middle
LongAdder разносит счёт по нескольким ячейкам: потоки не бьются за одну переменную, CAS не промахивается, кэш-линия не скачет между ядрами. Итог — сумма ячеек, а не мгновенно…
- Какие возможности даёт ReentrantLock, недоступные при использовании synchronized? Middle
ReentrantLock добавляет tryLock (с таймаутом), lockInterruptibly, справедливый режим и несколько Condition. Цена — освобождение вручную: lock() перед try, unlock() в finally.
- Что даёт оптимистичное чтение в StampedLock и о каком его ограничении важно помнить? Senior
tryOptimisticRead даёт метку, данные читаются без блокировки, затем validate проверяет, не было ли записи; не прошла — повтор или обычная блокировка чтения. Ограничения: данные…
- В parallel stream выполняется обращение к внешнему HTTP-сервису. Почему это опасно для всего приложения? Middle
Parallel stream выполняется в общем ForkJoinPool.commonPool — одном на JVM, размером «ядра − 1». Блокирующий HTTP-вызов занимает его потоки, и встают все parallel stream и…
- В каких случаях parallel stream, скорее всего, не даст выигрыша или окажется медленнее последовательного? Middle
Выигрыша нет, когда мало данных или дешёвая операция (N × Q мало), источник плохо делится (LinkedList, Stream.iterate, итератор), есть сохранение порядка (forEachOrdered, limit…
- Чем различаются exceptionally, handle и whenComplete у CompletableFuture? Middle
exceptionally — только при ошибке, подменяет результат. handle — вызывается всегда, получает значение и исключение и возвращает новый результат. whenComplete — побочный эффект…
- Чем вызов start() у потока отличается от прямого вызова run()? Junior
start() создаёт новый поток, и уже он вызывает run(); прямой run() выполняется в текущем потоке, как обычный метод.
- Что обеспечивает ключевое слово synchronized? Junior
synchronized даёт взаимное исключение (один поток в блоке по данному монитору) и видимость изменений для следующего вошедшего.
- Что делает класс AtomicInteger? Junior
AtomicInteger выполняет операции вроде инкремента атомарно без блокировок — на аппаратной инструкции CAS (сравнение с обменом).
- Где должен располагаться вызов unlock() при работе с ReentrantLock? Junior
unlock() — в finally, а lock() — перед try: иначе исключение в критической секции оставит замок захваченным навсегда.
- Почему задачи отдают в пул потоков, а не создают поток на каждую? Junior
Пул переиспользует потоки и ограничивает их число: создание потока дорого, а неограниченный рост убивает приложение.
- Что представляет собой объект Future? Junior
Future — ссылка на результат, который появится позже: можно проверить готовность, дождаться значения или отменить задачу.
- Что делает CountDownLatch? Junior
CountDownLatch — защёлка на счётчике: потоки ждут в await(), пока другие не уменьшат счётчик до нуля через countDown().
- Что даёт переменная типа ThreadLocal? Junior
ThreadLocal — своя ячейка значения для каждого потока: синхронизация не нужна, потому что значение никто не разделяет.
- Чем виртуальный поток отличается от обычного платформенного? Junior
Виртуальный поток — лёгкий поток под управлением JVM, а не ОС: создание и блокировка стоят копейки, их можно держать миллионами.
- Два потока одновременно вызывают incrementAndGet() у одного AtomicInteger. Что при этом происходит внутри и почему ни один инкремент не теряется? Middle
CAS-цикл: прочитать значение, вычислить новое и записать, только если в памяти всё ещё старое (одна атомарная инструкция процессора). Проиграл гонку — повторить с новым значением…