Skip to content

Вопросы на собеседовании: Java-разработчик

100 реальных вопросов с образцовыми ответами и пояснениями для уровня Middle.

Смотреть пример резюме: Java-разработчик

Тренировка флешкарточками

Интервальное повторение · Hunter Pass

Вопросы

concurrencysystem-design

Поток представляет собой путь выполнения внутри процесса и разделяет с другими потоками этого процесса кучу и ресурсы.

  • У каждого процесса изолированное адресное пространство, а потоки одной JVM могут обращаться к общим объектам.
  • У каждого потока собственные стек, счётчик команд и кадры вызовов, хотя куча остаётся общей.
  • Общая память упрощает обмен данными, но при конкурентном доступе к изменяемому состоянию требует синхронизации.

Зачем это спрашивают: Интервьюер проверяет, связывает ли кандидат базовую модель выполнения с рисками конкурентного доступа к общему состоянию в Java.

concurrency

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

  • После start() метод run() выполняется в новом потоке, когда планировщик переведёт его к исполнению.
  • Прямой вызов run() не создаёт конкурентности и блокирует вызывающий поток до возврата из метода.
  • Экземпляр Thread можно запустить только один раз, а повторный start() выбросит IllegalThreadStateException.

Зачем это спрашивают: Сильный ответ отличает создание потока от вызова метода и учитывает правило однократного запуска.

concurrency

Thread.State описывает этап жизненного цикла потока, а не то, занимает ли он процессорное ядро прямо сейчас.

  • После start() поток переходит из NEW в RUNNABLE, а после завершения или ошибки в run() попадает в TERMINATED.
  • Ожидание занятого synchronized-блока даёт BLOCKED, а Object.wait() или join() без тайм-аута переводит поток в WAITING.
  • sleep(), ожидание с тайм-аутом и join() с тайм-аутом дают TIMED_WAITING до истечения времени или другого условия пробуждения.

Зачем это спрашивают: Интервьюер проверяет, различает ли кандидат конкуренцию за монитор, бессрочное и ограниченное ожидание.

concurrency

Состояние гонки возникает, когда результат зависит от неконтролируемого порядка выполнения конкурентных операций.

  • count++ состоит из чтения, изменения и записи, а не является одной неделимой операцией.
  • Два потока могут прочитать одно старое значение, вычислить одинаковый результат и затереть обновление друг друга.
  • Операцию нужно защитить общим lock или использовать атомарный счётчик для конкурентных обновлений.

Зачем это спрашивают: Интервьюер хочет увидеть рассуждение о составных операциях, а не предположение, что одно выражение в коде выполняется атомарно.

concurrency

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

  • В критической секции под конкретным монитором одновременно может находиться только один поток.
  • Освобождение монитора публикует предшествующие записи, а последующий захват делает их видимыми.
  • Мониторы Java реентерабельны, поэтому владеющий монитором поток может повторно войти в защищённый им блок без самоблокировки.

Зачем это спрашивают: Полный ответ охватывает и блокировку, и связь happens-before, а не сводит synchronized только к мьютексу.

oopconcurrencymodifiers

Выбор монитора зависит от объекта, указанного формой синхронизации явно или неявно.

  • Синхронизированный метод экземпляра блокирует this, поэтому вызовы на разных экземплярах не исключают друг друга.
  • Статический synchronized-метод блокирует объект Class, общий для всех экземпляров этого класса в одном загрузчике.
  • synchronized-блок захватывает монитор указанного выражения, что позволяет выбрать приватный lock и сузить критическую секцию.

Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат определить фактический объект блокировки и не синхронизироваться на разных мониторах по ошибке.

wait и уведомления координируют потоки через монитор объекта, а sleep лишь приостанавливает текущий поток.

  • wait() вызывается при удержании монитора и освобождает его, пока поток не проснётся и не захватит монитор снова.
  • notify() пробуждает одного произвольного ожидающего, а notifyAll() пробуждает всех, после чего они конкурируют за монитор.
  • sleep() не требует и не освобождает монитор, поэтому сон внутри synchronized может зря блокировать другие потоки.
  • Условие ожидания проверяют в цикле while из-за ложных пробуждений и возможного изменения условия до повторного захвата.

Зачем это спрашивают: Сильный ответ учитывает владение монитором, освобождение блокировки и обязательную повторную проверку условия.

concurrencymemory

volatile гарантирует видимость и упорядочивание чтений и записей одной переменной, но не делает составные действия атомарными.

  • Запись в volatile-переменную happens-before каждого последующего чтения этой же переменной.
  • JVM и процессор не могут переставлять связанные операции памяти через volatile-доступ так, чтобы нарушить эту гарантию.
  • volatile подходит для флагов состояния или безопасной публикации неизменяемого значения, если обновление не зависит от старого значения.

Зачем это спрашивают: Интервьюер проверяет, отделяет ли кандидат видимость и порядок от взаимного исключения и атомарности.

concurrency

volatile делает каждое чтение и запись видимыми, но инкремент всё равно состоит из нескольких операций, которые могут чередоваться.

  • Каждый поток может увидеть актуальный counter и всё равно потерять обновление между чтением и записью.
  • AtomicInteger.incrementAndGet() выполняет обновление атомарно, обычно через compare-and-set.
  • synchronized или Lock лучше подходят, когда инвариант охватывает несколько переменных или операций.

Зачем это спрашивают: Интервьюер оценивает, выбирает ли кандидат механизм синхронизации по полному инварианту, а не по одному полю.

memoryconcurrency

Happens-before гарантирует, что эффекты одного действия будут видны другому действию.

  • Порядок программы устанавливает такую связь между более ранними и поздними действиями внутри одного потока.
  • Освобождение монитора happens-before его последующего захвата, а запись в volatile-переменную happens-before её последующего чтения.
  • Thread.start() публикует предшествующие действия запущенному потоку, а успешный join() публикует действия завершившегося потока ожидавшему.
  • Без цепочки happens-before другой поток не обязан видеть записи, даже если в тестах код обычно работает.

Зачем это спрашивают: Сильный ответ называет конкретные связи упорядочивания и объясняет, почему одного фактического порядка по времени недостаточно.

Безопасная публикация делает состояние полностью созданного объекта надёжно видимым всем потокам, получившим ссылку на него.

  • Публикация через volatile-поле, синхронизированную передачу, статический инициализатор или конкурентную коллекцию создаёт нужное упорядочивание.
  • Корректно инициализированные final-поля имеют особые гарантии видимости, но это не делает изменяемые объекты по ссылкам неизменяемыми.
  • Утечка this из конструктора может открыть доступ к значениям по умолчанию или частично созданному состоянию.

Зачем это спрашивают: Интервьюер проверяет понимание того, что правильно создать объект недостаточно, если ссылка на него передаётся небезопасно.

jvmconcurrency

Компилятор, JVM и процессор могут переставлять операции ради производительности, пока поведение одного потока остаётся корректным.

  • При гонке данных другой поток может наблюдать записи в неожиданном порядке.
  • synchronized, volatile, правила жизненного цикла потоков и конкурентные утилиты вводят ограничения порядка, известные модели памяти.
  • Расчёт на тайминги или добавление sleep() не создают видимость и не заменяют отношение happens-before.

Зачем это спрашивают: Интервьюер оценивает, опирается ли кандидат на гарантии Java Memory Model, а не на кажущийся порядок выполнения.

lockingconcurrency

Для deadlock нужны исключительные ресурсы, удержание одних ресурсов при ожидании других, отсутствие принудительного освобождения и цикл зависимостей.

  • Код с захватом нескольких lock в разном порядке может создать циклическое ожидание.
  • Единый порядок захвата или один lock для общего инварианта устраняет такой цикл.
  • tryLock с тайм-аутом позволяет отказаться от захвата и повторить попытку, но ветка отказа должна освободить все уже полученные lock.

Зачем это спрашивают: Сильный ответ раскрывает исходные условия и способы предотвращения, а не только даёт определение deadlock.

lockingconcurrency

При starvation один поток не получает прогресса, а при livelock потоки активны, но не выполняют полезную работу.

  • Starvation возможен, если поток постоянно проигрывает конкуренцию за несправедливый lock или ресурсы executor.
  • При livelock потоки отвечают друг другу повторными уступками или попытками в синхронном ритме.
  • Потоки в deadlock заблокированы циклом зависимостей, а при starvation или livelock их состояние может продолжать меняться.

Зачем это спрашивают: Интервьюер проверяет, различает ли кандидат несколько проблем живости, требующих разных решений.

concurrency

Lock полезен, когда коду нужно управление захватом, которого не предоставляет конструкция synchronized.

  • tryLock поддерживает немедленный отказ или тайм-аут, а lockInterruptibly позволяет отменить ожидание.
  • Один Lock может создать несколько очередей Condition, тогда как у монитора объекта только один набор ожидающих.
  • Для лексической критической секции synchronized обычно проще, потому что монитор освобождается автоматически даже при исключении.
  • Lock после успешного захвата всегда нужно освобождать в блоке finally.

Зачем это спрашивают: Интервьюер оценивает, знает ли кандидат возможности Lock и не заменяет ли им более простой synchronized по привычке.

ReentrantLock представляет собой явный реентерабельный мьютекс с прерываемым, ограниченным по времени и наблюдаемым захватом.

  • Реентерабельность учитывается счётчиком, поэтому владелец должен вызвать unlock столько раз, сколько успешно вызвал lock.
  • Справедливый lock при конкуренции обычно передаёт доступ потоку, ожидающему дольше остальных.
  • Справедливость может снижать starvation, но часто уменьшает пропускную способность, а tryLock без тайм-аута может обойти очередь.

Зачем это спрашивают: Сильный ответ объясняет и преимущества API, и влияние справедливости на пропускную способность.

ReadWriteLock может увеличить конкурентность, когда чтений много, записей мало, а защищённая работа достаточно велика.

  • Несколько читателей могут одновременно удерживать read lock, но write lock требует исключительного доступа.
  • Накладные расходы на lock и конкуренцию могут сделать обычный мьютекс быстрее для коротких секций или частых записей.
  • ReentrantReadWriteLock не поддерживает прямое повышение read lock до write lock, и попытка сохранить read lock при повышении может вызвать deadlock.

Зачем это спрашивают: Интервьюер проверяет, оценивает ли кандидат профиль нагрузки и риск повышения lock, а не считает ли большее число lock автоматическим ускорением.

concurrency

Атомарные классы сравнивают текущее значение с ожидаемым и заменяют его только при совпадении.

  • Неудачный compare-and-set означает вмешательство другого потока, поэтому lock-free алгоритм обычно перечитывает значение и повторяет попытку.
  • Операция атомарна и даёт семантику видимости уровня volatile без захвата монитора Java.
  • ABA возникает, если значение меняется с A на B и обратно на A, поэтому сравнение видит A, но пропускает промежуточное изменение.
  • AtomicStampedReference или AtomicMarkableReference добавляет версию, когда история изменений значима.

Зачем это спрашивают: Сильный ответ не ограничивается названием CAS и учитывает проблему сравнения только текущих значений.

concurrency

AtomicLong хранит одно атомарно читаемое значение, а LongAdder распределяет конкурентные обновления по внутренним ячейкам.

  • AtomicLong подходит, когда нужны операции compare-and-set или каждое чтение должно давать единое линеаризуемое значение.
  • LongAdder обычно лучше масштабируется для часто обновляемых метрик, потому что потоки конкурируют за разные ячейки.
  • LongAdder.sum() объединяет ячейки и не является атомарным снимком на фоне обновлений, поэтому не подходит для логики координации.

Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат выбирать между точным атомарным наблюдением и пропускной способностью обновлений.

concurrency

ExecutorService отделяет отправку задачи от политики создания, переиспользования, планирования и остановки потоков.

  • Переиспользование ограниченного набора workers снижает расходы памяти и переключений контекста по сравнению с отдельным потоком на задачу.
  • Executor умеет ставить работу в очередь, отклонять перегрузку, возвращать Future и управлять жизненным циклом.
  • Приложение всё равно должно выбрать ёмкость очереди, размер пула, фабрику потоков и политику отказа под свою нагрузку.

Зачем это спрашивают: Интервьюер проверяет понимание executor как политики управления ресурсами, а не просто удобного синтаксиса.

Закрытые вопросы

  • 21

    Как выбирать размер пула потоков для CPU-bound и I/O-bound задач?

    concurrency
  • 22

    Чем отличаются execute() и submit() у ExecutorService?

    concurrency
  • 23

    Как правильно останавливать ExecutorService?

    concurrency
  • 24

    Чем отличаются Future и CompletableFuture?

    concurrency
  • 25

    Как работает модель work stealing в ForkJoinPool?

  • 26

    Почему обобщённые типы Java инвариантны?

    generics
  • 27

    Что означает правило PECS для ограниченных wildcard?

    generics
  • 28

    Когда обобщённому методу нужен параметр типа вместо wildcard?

    generics
  • 29

    Как в Java работают ограниченные параметры типа и пересечения ограничений?

  • 30

    Что такое стирание типов и какие ограничения оно накладывает на generics в Java?

    generics
  • 31

    Что такое raw types и heap pollution?

    data-structuresgenericsmemory
  • 32

    Почему в Java можно создать String[], но нельзя new List<String>[]?

    strings
  • 33

    Какой интерфейс считается функциональным и как концептуально представлены lambda-выражения?

    typeslambdalambdas
  • 34

    Почему lambda может захватывать только final или effectively final локальные переменные?

    lambdalambdasmodifiers
  • 35

    Чем отличаются промежуточные и терминальные операции Stream?

  • 36

    Чем map() отличается от flatMap() в Stream API?

    streams
  • 37

    Когда в Stream нужно использовать reduce(), а когда collect()?

  • 38

    Почему побочные эффекты опасны в Stream pipeline, особенно в параллельном?

    ci-cd
  • 39

    Как порядок обхода и short-circuiting влияют на выполнение Stream?

  • 40

    Как современный HashMap находит значение по ключу?

    collections
  • 41

    Какой контракт должны соблюдать equals() и hashCode() для hash-коллекций?

    collectionsequals-hashcode
  • 42

    Что происходит при resize HashMap и как на него влияют capacity и load factor?

    capacitycollections
  • 43

    Как ConcurrentHashMap поддерживает конкурентный доступ и чем отличается от HashMap?

    concurrencycollections
  • 44

    Чем отличаются fail-fast, snapshot и weakly consistent итераторы?

    snapshotiteration
  • 45

    Как JVM определяет, можно ли собрать объект как мусор?

    gcjvmcollections
  • 46

    Почему JVM collectors используют поколения и что такое minor и major collections?

    jvmcollections
  • 47

    Чем отличаются паттерны Strategy и Template Method?

  • 48

    Когда полезны Factory Method и Builder и какую проблему решает каждый из них?

  • 49

    Что такое IoC и dependency injection в Spring Core?

    springinjectiondependencies
  • 50

    Как Spring создаёт и инициализирует singleton bean и где может сломаться dependency injection?

    springinjectiondependencies
  • 51

    Два запроса иногда перезаписывают изменения друг друга в одном объекте в памяти. Как вы найдёте и исправите состояние гонки?

    memoryconcurrency
  • 52

    Spring-сервис перестаёт отвечать под нагрузкой, хотя CPU почти не занят, а все потоки запросов ждут. Что вы будете исследовать?

    concurrencydebuggingspring
  • 53

    Дампы потоков в production показывают два Java-потока, которые бесконечно ждут блокировки друг друга. Как вы устраните дедлок?

    concurrencylocking
  • 54

    После добавления синхронизации корректность улучшилась, но пропускная способность резко упала. Как безопасно снизить конкуренцию за блокировку?

    throughput
  • 55

    Цепочка CompletableFuture иногда зависает, а иногда скрывает исходное исключение. Как сделать её надёжной?

    error-handlingconcurrencyexceptions
  • 56

    Кеш на основе HashMap иногда повреждает данные при конкурентном доступе. Чем и как вы его замените?

    concurrencycollectionscaching
  • 57

    Стоит ли переводить блокирующий Spring Boot-сервис на виртуальные потоки Java ради большего числа одновременных запросов?

    concurrencyspring
  • 58

    Процесс Spring Boot растёт несколько дней, а затем падает с OutOfMemoryError. Как вы найдёте утечку?

    springconcurrency
  • 59

    Java-сервис внезапно занимает целое ядро CPU без роста трафика. В какой последовательности вы будете искать причину?

  • 60

    Пользователи замечают периодические скачки задержки, совпадающие с долгими паузами сборщика мусора JVM. Как улучшить ситуацию?

    latencygcjvm
  • 61

    У одного REST-эндпоинта плохая задержка p99, хотя средняя задержка нормальна. Как найти медленный участок?

    restendpointslatency
  • 62

    Эндпоинт Spring Data выполняет сотни запросов при возврате страницы заказов. Как исправить это, не сломав пагинацию?

    endpointspaginationqueries
  • 63

    Запрос PostgreSQL из Hibernate замедляется по мере роста таблицы. Как вы его оптимизируете?

    queriespostgresorm
  • 64

    Ночной Java-процесс загружает десять миллионов строк в память и падает. Как вы его перепроектируете?

    batchmemory
  • 65

    Вы добавляете Redis-кеш к эндпоинту товаров с большим числом чтений. Как избежать устаревших и опасных данных в кеше?

    cachingendpointsredis
  • 66

    Запросы завершаются по тайм-ауту из-за исчерпания пула соединений HikariCP. Что вы будете делать?

    pooling
  • 67

    Класс Spring-сервиса вырос до 2000 строк и смешивает валидацию, хранение, маппинг и внешние вызовы. Как безопасно его отрефакторить?

    refactoringvalidationspring
  • 68

    В legacy-модуле Java проверки null и обработка NullPointerException разбросаны по всем вызывающим местам. Как это улучшить?

    spreadfundamentals
  • 69

    Пайплайн Stream API лаконичен, но медленный и плохо отлаживается на горячем пути. Стоит ли его оставить?

    ci-cdapistreams
  • 70

    Контроллеры дублируют try-catch и возвращают разные тела ошибок. Как отрефакторить REST-слой Spring?

    refactoringrestspring
  • 71

    Нужно выделить один модуль из Spring-монолита, но его таблицы и сервисы тесно связаны. Как вы подойдёте к задаче?

    monolithspring
  • 72

    Тест JUnit проходит локально, но периодически падает в CI. Как вы его отладите и стабилизируете?

    unitjunit
  • 73

    Как вы напишете модульный тест Spring-сервиса, который рассчитывает цену, сохраняет заказ и отправляет уведомление?

    hypothesis-testingspring
  • 74

    Тест Mockito проверяет каждый getter и точный порядок вызовов, поэтому ломается при безобидном рефакторинге. Как его улучшить?

    refactoringmocking
  • 75

    Когда стоит заменить тест репозитория с моками на интеграционный тест с настоящей базой данных?

    integrationdatabase
  • 76

    У метода расчёта цены много комбинаций входных данных и граничных случаев. Как покрыть его в JUnit 5 без дублирования тестов?

    junitpricing
  • 77

    Интеграционный тест Spring проходит, потому что каждый тест откатывается, но production падает при коммите транзакции. Как проявить реальное поведение?

    transactionsintegrationspring
  • 78

    Spring-метод запускает асинхронную работу и сразу возвращается. Как протестировать его без Thread.sleep?

    asyncconcurrencyspring
  • 79

    Клиенты повторяют POST /orders после сетевых тайм-аутов и иногда создают дубликаты заказов. Как исправить API?

    resilienceapi
  • 80

    Как спроектировать валидацию и ответы с ошибками для Spring REST-эндпоинта создания клиента?

    designspringrest
  • 81

    Нужно изменить популярный REST-ответ, не сломав существующих клиентов. Каков ваш план выпуска?

    rest
  • 82

    API событий замедляется и пропускает записи, пока пользователи листают часто меняющиеся данные. Как перепроектировать пагинацию?

    paginationrecords
  • 83

    Два пользователя редактируют один товар, и последнее сохранение незаметно побеждает. Как предотвратить потерю обновлений в Spring Data JPA?

    ormspring
  • 84

    Денежный перевод читает два баланса и обновляет их в одной Spring-транзакции. Какие конкурентные риски вы устраните?

    concurrencytransactionsspring
  • 85

    Метод с @Transactional вызывается другим методом того же Spring-бина, но транзакция не начинается. Как это исправить?

    transactionsspring
  • 86

    Spring-транзакция сохраняет заказ, а затем публикует событие Kafka, но сбой может рассогласовать базу и брокер. Как это исправить?

    databasetransactionskafka
  • 87

    REST-эндпоинт зависит от медленного внешнего платёжного API. Как не дать этой зависимости исчерпать ресурсы сервиса?

    restendpointsdependencies
  • 88

    После изменения фичи Spring сообщает о циклической зависимости двух сервисов. Как вы её устраните?

    springdependencies
  • 89

    API на Spring Security возвращает 403 для истёкшего токена и 401 для аутентифицированного пользователя без нужной роли. Как исправить ответы?

    tokensapispring
  • 90

    Как реализовать загрузку большого файла в Spring Boot без исчерпания памяти и новых уязвимостей?

    springendpointsmemory
  • 91

    Kafka consumer иногда повторно применяет одно событие после перезапуска. Как сделать обработку безопасной?

    kafkaconcurrency
  • 92

    Одно повреждённое сообщение Kafka бесконечно повторяется и блокирует партицию. Как вы его обработаете?

    partitioningkafka
  • 93

    Как выбрать между REST и gRPC для связи двух внутренних Java-сервисов?

    restgrpc
  • 94

    Kubernetes завершает pod Spring Boot во время деплоя, и часть запросов падает. Как реализовать graceful shutdown?

    lifecyclespringkubernetes
  • 95

    Как настроить health checks Spring Boot, чтобы Kubernetes не перезапускал исправный сервис при временном сбое зависимости?

    kubernetesconfighealth-checks
  • 96

    Java-контейнер завершается из-за превышения лимита памяти, хотя заданный heap меньше лимита. Что вы измените?

    memorycontainersconfig
  • 97

    После включения параллельного запуска JUnit в CI тесты ускорились, но начали мешать друг другу. Как сохранить ускорение?

    junit
  • 98

    Во время инцидента вы нашли токены доступа и данные клиентов в логах Spring-приложения. Как исправить логирование?

    incidentsloggingtokens
  • 99

    Pull request заменяет понятный код сложным кешем и заявляет ускорение в 10 раз по микробенчмарку. Как вы его проверите?

    code-reviewcachingcapacity
  • 100

    Новый релиз удвоил число ошибок и задержку Java-сервиса. Как вы проведёте production-инцидент и последующую работу?

    incidentslatency