Вопросы на собеседовании: Java-разработчик
100 реальных вопросов с образцовыми ответами и пояснениями для уровня Middle.
Смотреть пример резюме: Java-разработчик →Тренировка флешкарточками
Интервальное повторение · Hunter Pass
Вопросы
Поток представляет собой путь выполнения внутри процесса и разделяет с другими потоками этого процесса кучу и ресурсы.
- У каждого процесса изолированное адресное пространство, а потоки одной JVM могут обращаться к общим объектам.
- У каждого потока собственные стек, счётчик команд и кадры вызовов, хотя куча остаётся общей.
- Общая память упрощает обмен данными, но при конкурентном доступе к изменяемому состоянию требует синхронизации.
Зачем это спрашивают: Интервьюер проверяет, связывает ли кандидат базовую модель выполнения с рисками конкурентного доступа к общему состоянию в Java.
start() просит JVM создать новый путь выполнения, а run() является обычным вызовом метода в текущем потоке.
- После start() метод run() выполняется в новом потоке, когда планировщик переведёт его к исполнению.
- Прямой вызов run() не создаёт конкурентности и блокирует вызывающий поток до возврата из метода.
- Экземпляр Thread можно запустить только один раз, а повторный start() выбросит IllegalThreadStateException.
Зачем это спрашивают: Сильный ответ отличает создание потока от вызова метода и учитывает правило однократного запуска.
Thread.State описывает этап жизненного цикла потока, а не то, занимает ли он процессорное ядро прямо сейчас.
- После start() поток переходит из NEW в RUNNABLE, а после завершения или ошибки в run() попадает в TERMINATED.
- Ожидание занятого synchronized-блока даёт BLOCKED, а Object.wait() или join() без тайм-аута переводит поток в WAITING.
- sleep(), ожидание с тайм-аутом и join() с тайм-аутом дают TIMED_WAITING до истечения времени или другого условия пробуждения.
Зачем это спрашивают: Интервьюер проверяет, различает ли кандидат конкуренцию за монитор, бессрочное и ограниченное ожидание.
Состояние гонки возникает, когда результат зависит от неконтролируемого порядка выполнения конкурентных операций.
- count++ состоит из чтения, изменения и записи, а не является одной неделимой операцией.
- Два потока могут прочитать одно старое значение, вычислить одинаковый результат и затереть обновление друг друга.
- Операцию нужно защитить общим lock или использовать атомарный счётчик для конкурентных обновлений.
Зачем это спрашивают: Интервьюер хочет увидеть рассуждение о составных операциях, а не предположение, что одно выражение в коде выполняется атомарно.
synchronized обеспечивает взаимное исключение и видимость памяти для кода, защищённого одним монитором.
- В критической секции под конкретным монитором одновременно может находиться только один поток.
- Освобождение монитора публикует предшествующие записи, а последующий захват делает их видимыми.
- Мониторы Java реентерабельны, поэтому владеющий монитором поток может повторно войти в защищённый им блок без самоблокировки.
Зачем это спрашивают: Полный ответ охватывает и блокировку, и связь happens-before, а не сводит synchronized только к мьютексу.
Выбор монитора зависит от объекта, указанного формой синхронизации явно или неявно.
- Синхронизированный метод экземпляра блокирует this, поэтому вызовы на разных экземплярах не исключают друг друга.
- Статический synchronized-метод блокирует объект Class, общий для всех экземпляров этого класса в одном загрузчике.
- synchronized-блок захватывает монитор указанного выражения, что позволяет выбрать приватный lock и сузить критическую секцию.
Зачем это спрашивают: Интервьюер проверяет, умеет ли кандидат определить фактический объект блокировки и не синхронизироваться на разных мониторах по ошибке.
wait и уведомления координируют потоки через монитор объекта, а sleep лишь приостанавливает текущий поток.
- wait() вызывается при удержании монитора и освобождает его, пока поток не проснётся и не захватит монитор снова.
- notify() пробуждает одного произвольного ожидающего, а notifyAll() пробуждает всех, после чего они конкурируют за монитор.
- sleep() не требует и не освобождает монитор, поэтому сон внутри synchronized может зря блокировать другие потоки.
- Условие ожидания проверяют в цикле while из-за ложных пробуждений и возможного изменения условия до повторного захвата.
Зачем это спрашивают: Сильный ответ учитывает владение монитором, освобождение блокировки и обязательную повторную проверку условия.
volatile гарантирует видимость и упорядочивание чтений и записей одной переменной, но не делает составные действия атомарными.
- Запись в volatile-переменную happens-before каждого последующего чтения этой же переменной.
- JVM и процессор не могут переставлять связанные операции памяти через volatile-доступ так, чтобы нарушить эту гарантию.
- volatile подходит для флагов состояния или безопасной публикации неизменяемого значения, если обновление не зависит от старого значения.
Зачем это спрашивают: Интервьюер проверяет, отделяет ли кандидат видимость и порядок от взаимного исключения и атомарности.
volatile делает каждое чтение и запись видимыми, но инкремент всё равно состоит из нескольких операций, которые могут чередоваться.
- Каждый поток может увидеть актуальный counter и всё равно потерять обновление между чтением и записью.
- AtomicInteger.incrementAndGet() выполняет обновление атомарно, обычно через compare-and-set.
- synchronized или Lock лучше подходят, когда инвариант охватывает несколько переменных или операций.
Зачем это спрашивают: Интервьюер оценивает, выбирает ли кандидат механизм синхронизации по полному инварианту, а не по одному полю.
Happens-before гарантирует, что эффекты одного действия будут видны другому действию.
- Порядок программы устанавливает такую связь между более ранними и поздними действиями внутри одного потока.
- Освобождение монитора happens-before его последующего захвата, а запись в volatile-переменную happens-before её последующего чтения.
- Thread.start() публикует предшествующие действия запущенному потоку, а успешный join() публикует действия завершившегося потока ожидавшему.
- Без цепочки happens-before другой поток не обязан видеть записи, даже если в тестах код обычно работает.
Зачем это спрашивают: Сильный ответ называет конкретные связи упорядочивания и объясняет, почему одного фактического порядка по времени недостаточно.
Безопасная публикация делает состояние полностью созданного объекта надёжно видимым всем потокам, получившим ссылку на него.
- Публикация через volatile-поле, синхронизированную передачу, статический инициализатор или конкурентную коллекцию создаёт нужное упорядочивание.
- Корректно инициализированные final-поля имеют особые гарантии видимости, но это не делает изменяемые объекты по ссылкам неизменяемыми.
- Утечка this из конструктора может открыть доступ к значениям по умолчанию или частично созданному состоянию.
Зачем это спрашивают: Интервьюер проверяет понимание того, что правильно создать объект недостаточно, если ссылка на него передаётся небезопасно.
Компилятор, JVM и процессор могут переставлять операции ради производительности, пока поведение одного потока остаётся корректным.
- При гонке данных другой поток может наблюдать записи в неожиданном порядке.
- synchronized, volatile, правила жизненного цикла потоков и конкурентные утилиты вводят ограничения порядка, известные модели памяти.
- Расчёт на тайминги или добавление sleep() не создают видимость и не заменяют отношение happens-before.
Зачем это спрашивают: Интервьюер оценивает, опирается ли кандидат на гарантии Java Memory Model, а не на кажущийся порядок выполнения.
Для deadlock нужны исключительные ресурсы, удержание одних ресурсов при ожидании других, отсутствие принудительного освобождения и цикл зависимостей.
- Код с захватом нескольких lock в разном порядке может создать циклическое ожидание.
- Единый порядок захвата или один lock для общего инварианта устраняет такой цикл.
- tryLock с тайм-аутом позволяет отказаться от захвата и повторить попытку, но ветка отказа должна освободить все уже полученные lock.
Зачем это спрашивают: Сильный ответ раскрывает исходные условия и способы предотвращения, а не только даёт определение deadlock.
При starvation один поток не получает прогресса, а при livelock потоки активны, но не выполняют полезную работу.
- Starvation возможен, если поток постоянно проигрывает конкуренцию за несправедливый lock или ресурсы executor.
- При livelock потоки отвечают друг другу повторными уступками или попытками в синхронном ритме.
- Потоки в deadlock заблокированы циклом зависимостей, а при starvation или livelock их состояние может продолжать меняться.
Зачем это спрашивают: Интервьюер проверяет, различает ли кандидат несколько проблем живости, требующих разных решений.
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 автоматическим ускорением.
Атомарные классы сравнивают текущее значение с ожидаемым и заменяют его только при совпадении.
- Неудачный compare-and-set означает вмешательство другого потока, поэтому lock-free алгоритм обычно перечитывает значение и повторяет попытку.
- Операция атомарна и даёт семантику видимости уровня volatile без захвата монитора Java.
- ABA возникает, если значение меняется с A на B и обратно на A, поэтому сравнение видит A, но пропускает промежуточное изменение.
- AtomicStampedReference или AtomicMarkableReference добавляет версию, когда история изменений значима.
Зачем это спрашивают: Сильный ответ не ограничивается названием CAS и учитывает проблему сравнения только текущих значений.
AtomicLong хранит одно атомарно читаемое значение, а LongAdder распределяет конкурентные обновления по внутренним ячейкам.
- AtomicLong подходит, когда нужны операции compare-and-set или каждое чтение должно давать единое линеаризуемое значение.
- LongAdder обычно лучше масштабируется для часто обновляемых метрик, потому что потоки конкурируют за разные ячейки.
- LongAdder.sum() объединяет ячейки и не является атомарным снимком на фоне обновлений, поэтому не подходит для логики координации.
Зачем это спрашивают: Интервьюер оценивает, умеет ли кандидат выбирать между точным атомарным наблюдением и пропускной способностью обновлений.
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