Подборка вопросов

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

synchronized, volatile, пулы потоков, CompletableFuture и Java Memory Model.

42вопроса
10Junior
27Middle
5Senior
  1. 01Что гарантирует ключевое слово volatile для переменной?

    volatile даёт видимость (запись сразу видна другим потокам) и запрет переупорядочивания вокруг обращения, но не атомарность: count++ на volatile-поле всё равно теряет обновления.

    Middle
  2. 02Чем volatile отличается от synchronized?

    volatile — видимость и порядок для одной переменной без блокировки. synchronized — видимость плюс взаимное исключение: блок целиком выполняется одним потоком, поэтому составные…

    Middle
  3. 03Чем Thread, Runnable и Callable отличаются?

    Thread — сам поток выполнения. Runnable — задача без результата (void run()), Callable<V> — задача с результатом V call(), которая может бросить проверяемое исключение.

    Junior
  4. 04В чём опасность ThreadLocal в пуле потоков?

    Поток в пуле переиспользуется: значение ThreadLocal, не очищенное после задачи, протекает в следующую задачу (чужой пользователь, транзакция) и удерживает память. Правило: try {…

    Middle
  5. 05Что такое deadlock и как его предотвращать?

    Deadlock — циклическое ожидание: поток A держит замок 1 и ждёт замок 2, поток B держит 2 и ждёт 1. Предотвращают единым порядком захвата, tryLock с таймаутом и откатом, короткими…

    Middle
  6. 06Чем различаются CountDownLatch, CyclicBarrier и Semaphore?

    CountDownLatch — одноразовое ожидание N событий: дошёл до нуля — открыт навсегда. CyclicBarrier — переиспользуемая точка встречи N потоков между раундами. Semaphore — ограничение…

    Middle
  7. 07Что такое Fork/Join framework и work stealing?

    Fork/Join — рекурсивное деление задачи на подзадачи (fork) с последующим объединением результатов (join). Work stealing: у каждого рабочего потока своя двусторонняя очередь, а…

    Senior
  8. 08Реализуй потокобезопасный счётчик Counter с методами increment() и get().

    Проще всего — AtomicInteger: incrementAndGet() и get(), без блокировок. Альтернатива — synchronized на обоих методах (важно: и на чтении тоже, иначе нет видимости).

    Middle
  9. 09Какое ограничение Future решает CompletableFuture?

    Future — дескриптор результата с единственным способом получить значение: блокирующий get(). CompletableFuture позволяет описать цепочку продолжений (thenApply, thenCompose…

    Middle
  10. 10Чем thenCompose отличается от thenApply в CompletableFuture?

    thenApply — как map: функция значение → значение. thenCompose — как flatMap: функция значение → новый CompletableFuture, результат «разворачивается». Без thenCompose получилось бы…

    Middle
  11. 11Почему опасно выполнять блокирующие операции в стадиях CompletableFuture без явного Executor?

    Async-стадии без Executor выполняются в ForkJoinPool.commonPool — он общий на всю JVM и размером примерно число ядер − 1. Блокирующие вызовы (HTTP, БД, get чужого future) занимают…

    Senior
  12. 12Поток пытается войти в synchronized-блок, занятый другим потоком. В каком состоянии он находится?

    BLOCKED — ожидание входа в занятый монитор synchronized. Шесть состояний: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED.

    Middle
  13. 13Что верно про прерывание потока через interrupt()?

    interrupt() — просьба, а не приказ: выставляет флаг; блокирующие sleep, wait, join бросают InterruptedException и сбрасывают флаг. Поток проверяет флаг сам и завершается в…

    Middle
  14. 14Что верно про daemon-потоки?

    JVM завершается, когда остались только daemon-потоки; они обрываются без выполнения finally. Флаг ставится до start(), иначе IllegalThreadStateException.

    Middle
  15. 15Почему фабрики из класса Executors считают опасными в production-коде?

    Скрытые неограниченные ресурсы: newFixedThreadPool — неограниченная очередь (задачи копятся до OutOfMemoryError, наружу не идёт никакого отказа), newCachedThreadPool …

    Middle
  16. 16Сервис в основном ходит по сети: на каждую задачу примерно 90% времени — ожидание ответа и 10% — вычисления. Как выбрать размер пула потоков?

    IO-bound: ядра × (1 + W/C), где W — ожидание, C — вычисления; при 90/10 — около 10 × ядер. CPU-bound — примерно по числу ядер. Это ориентир: реальный предел часто задаёт внешний…

    Middle
  17. 17Задача, отправленная в пул через submit(), бросает RuntimeException, но в логах ничего нет. Почему?

    submit() прячет исключение в Future: оно появится только при get() в ExecutionException. Если результат никто не запрашивает — ошибка исчезает бесследно. execute() пропускает…

    Middle
  18. 18Нужно ограничить число одновременных обращений к внешнему API десятью, не ограничивая при этом число рабочих потоков. Какой примитив подходит?

    Semaphore с десятью разрешениями: acquire перед вызовом, release в finally. Он ограничивает число одновременных доступов к ресурсу, не трогая число потоков.

    Middle
  19. 19Можно ли переиспользовать CountDownLatch после того, как счётчик дошёл до нуля?

    Нет: дошедший до нуля CountDownLatch открыт навсегда, метода сброса нет. Для повторяющихся раундов — CyclicBarrier или Phaser; либо новый latch на каждый раунд.

    Middle
  20. 20В коде вызывается semaphore.acquire(), затем работа с ресурсом, затем semaphore.release() в конце метода. Что здесь опасно?

    release не в finally: исключение навсегда съедает разрешение. С каждым сбоем параллелизм падает на единицу, пока система не встанет без единой записи в логе.

    Middle
  21. 21Что верно про практическое применение виртуальных потоков?

    Не пулить (поток на задачу), применять для блокирующего ввода-вывода, помнить, что ThreadLocal на миллионах потоков дорог, и ограничивать доступ к внешним ресурсам семафором — пул…

    Middle
  22. 22Что такое pinning виртуального потока и в каких случаях он всё ещё возможен в актуальных версиях Java?

    Pinning — виртуальный поток при блокировке не может отцепиться от несущего, и тот простаивает. До JDK 24 главная причина — блокировка внутри synchronized; JEP 491 (JDK 24) это…

    Senior
  23. 23Что происходит с виртуальным потоком, когда он выполняет блокирующий сетевой вызов?

    Unmount: стек виртуального потока (continuation) сохраняется в куче, несущий поток освобождается для других задач. Когда данные пришли — mount обратно, возможно на другой несущий…

    Middle
  24. 24Почему вызов wait() принято помещать в цикл while, а не в if?

    Пробуждение не гарантирует, что условие выполнено: возможны ложные пробуждения (spurious wakeup), а между notify и захватом монитора другой поток мог снова изменить состояние…

    Middle
  25. 25На каких объектах не следует синхронизироваться?

    Не синхронизироваться на разделяемых или подменяемых объектах: строковых литералах (интернированы), Integer и других обёртках (кэш значений), публичном this, изменяемом поле…

    Middle
  26. 26Что такое ABA-проблема в алгоритмах на CAS?

    CAS сравнивает только текущее значение, а не историю: A → B → A выглядит как «ничего не менялось». В lock-free структурах со ссылками это ломает данные (узел удалили и…

    Senior
  27. 27Чем LongAdder выигрывает у AtomicLong при высокой конкуренции и в чём его ограничение?

    LongAdder разносит счёт по нескольким ячейкам: потоки не бьются за одну переменную, CAS не промахивается, кэш-линия не скачет между ядрами. Итог — сумма ячеек, а не мгновенно…

    Middle
  28. 28Какие возможности даёт ReentrantLock, недоступные при использовании synchronized?

    ReentrantLock добавляет tryLock (с таймаутом), lockInterruptibly, справедливый режим и несколько Condition. Цена — освобождение вручную: lock() перед try, unlock() в finally.

    Middle
  29. 29Что даёт оптимистичное чтение в StampedLock и о каком его ограничении важно помнить?

    tryOptimisticRead даёт метку, данные читаются без блокировки, затем validate проверяет, не было ли записи; не прошла — повтор или обычная блокировка чтения. Ограничения: данные…

    Senior
  30. 30В parallel stream выполняется обращение к внешнему HTTP-сервису. Почему это опасно для всего приложения?

    Parallel stream выполняется в общем ForkJoinPool.commonPool — одном на JVM, размером «ядра − 1». Блокирующий HTTP-вызов занимает его потоки, и встают все parallel stream и…

    Middle
  31. 31В каких случаях parallel stream, скорее всего, не даст выигрыша или окажется медленнее последовательного?

    Выигрыша нет, когда мало данных или дешёвая операция (N × Q мало), источник плохо делится (LinkedList, Stream.iterate, итератор), есть сохранение порядка (forEachOrdered, limit…

    Middle
  32. 32Чем различаются exceptionally, handle и whenComplete у CompletableFuture?

    exceptionally — только при ошибке, подменяет результат. handle — вызывается всегда, получает значение и исключение и возвращает новый результат. whenComplete — побочный эффект…

    Middle
  33. 33Чем вызов start() у потока отличается от прямого вызова run()?

    start() создаёт новый поток, и уже он вызывает run(); прямой run() выполняется в текущем потоке, как обычный метод.

    Junior
  34. 34Что обеспечивает ключевое слово synchronized?

    synchronized даёт взаимное исключение (один поток в блоке по данному монитору) и видимость изменений для следующего вошедшего.

    Junior
  35. 35Что делает класс AtomicInteger?

    AtomicInteger выполняет операции вроде инкремента атомарно без блокировок — на аппаратной инструкции CAS (сравнение с обменом).

    Junior
  36. 36Где должен располагаться вызов unlock() при работе с ReentrantLock?

    unlock() — в finally, а lock() — перед try: иначе исключение в критической секции оставит замок захваченным навсегда.

    Junior
  37. 37Почему задачи отдают в пул потоков, а не создают поток на каждую?

    Пул переиспользует потоки и ограничивает их число: создание потока дорого, а неограниченный рост убивает приложение.

    Junior
  38. 38Что представляет собой объект Future?

    Future — ссылка на результат, который появится позже: можно проверить готовность, дождаться значения или отменить задачу.

    Junior
  39. 39Что делает CountDownLatch?

    CountDownLatch — защёлка на счётчике: потоки ждут в await(), пока другие не уменьшат счётчик до нуля через countDown().

    Junior
  40. 40Что даёт переменная типа ThreadLocal?

    ThreadLocal — своя ячейка значения для каждого потока: синхронизация не нужна, потому что значение никто не разделяет.

    Junior
  41. 41Чем виртуальный поток отличается от обычного платформенного?

    Виртуальный поток — лёгкий поток под управлением JVM, а не ОС: создание и блокировка стоят копейки, их можно держать миллионами.

    Junior
  42. 42Два потока одновременно вызывают incrementAndGet() у одного AtomicInteger. Что при этом происходит внутри и почему ни один инкремент не теряется?

    CAS-цикл: прочитать значение, вычислить новое и записать, только если в памяти всё ещё старое (одна атомарная инструкция процессора). Проиграл гонку — повторить с новым значением…

    Middle
Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования