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