Как пройти собеседование по Java: топ вопросов продвинутого уровня
Собеседование на позицию Java-разработчика продвинутого уровня (Middle/Senior) — это не просто проверка синтаксиса. Интервьюеры хотят увидеть глубокое понимание внутреннего устройства JVM, многопоточности, алгоритмов работы сборщика мусора и нюансов стандартной библиотеки. В этой статье мы разберем самые частые и сложные вопросы, которые помогут вам уверенно пройти техническое интервью.
1. Многопоточность и синхронизация: от volatile до Lock
Продвинутое собеседование по Java почти всегда начинается с вопросов о многопоточности. Вам нужно не просто знать ключевое слово synchronized, но и понимать, как работают volatile, AtomicInteger, ReentrantLock и что такое happens-before.
Вопрос: В чем разница между synchronized и ReentrantLock?
Ключевое отличие — гибкость. synchronized — это блокировка на уровне монитора объекта. ReentrantLock предоставляет расширенные возможности: возможность прервать ожидание потока (lockInterruptibly()), попытку захвата без блокировки (tryLock()), а также поддержку нескольких условий (Condition).
// Пример использования ReentrantLock с Condition
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
public class BlockingQueueExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private final Object[] items = new Object[10];
private int putIndex, takeIndex, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await(); // ждем, пока не освободится место
}
items[putIndex] = x;
if (++putIndex == items.length) putIndex = 0;
count++;
notEmpty.signal(); // сигналим, что элемент появился
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // ждем, пока не появится элемент
}
Object x = items[takeIndex];
if (++takeIndex == items.length) takeIndex = 0;
count--;
notFull.signal(); // сигналим, что место освободилось
return x;
} finally {
lock.unlock();
}
}
}
Вопрос: Как работает ключевое слово volatile?
volatile гарантирует, что чтение и запись переменной происходит непосредственно из основной памяти (main memory), минуя кэш процессора. Это обеспечивает видимость изменений для всех потоков, но не гарантирует атомарность (например, инкремент count++ все равно требует синхронизации).
Также стоит помнить про happens-before: запись в volatile переменную происходит до любого последующего чтения этой же переменной из другого потока.
2. Java Memory Model (JMM) и Garbage Collector
Понимание того, как JVM управляет памятью, — обязательное требование для Senior-разработчика. Интервьюеры часто спрашивают про разные поколения (Young/Old Generation), типы сборщиков мусора и настройку JVM.
Вопрос: Какие типы Garbage Collector существуют в Java 17?
Современная Java (17+) поддерживает несколько GC:
- G1GC (Garbage First) — GC по умолчанию. Разделяет кучу на регионы и собирает мусор в регионах с наибольшим количеством мертвых объектов.
- ZGC (Z Garbage Collector) — низколатентный GC, работающий практически без пауз (менее 10 мс), даже для больших куч (до 16 ТБ).
- Shenandoah — конкурентный GC с низким временем пауз, похож на ZGC, но реализован иначе.
- Parallel GC — классический многопоточный GC, оптимизированный для пропускной способности.
Вопрос: Что такое Stop-The-World и как его избежать?
Stop-The-World (STW) — это момент, когда все потоки приложения приостанавливаются для выполнения GC. Избежать полностью нельзя, но можно минимизировать, используя низколатентные GC (ZGC, Shenandoah) или правильно настраивая размеры поколений:
// Пример настройки JVM для уменьшения пауз GC
// Использование ZGC:
// -XX:+UseZGC -Xms4g -Xmx4g -XX:ConcGCThreads=2
3. Коллекции: ConcurrentHashMap и WeakHashMap
Вопросы про коллекции на продвинутом уровне выходят за рамки "чем отличается ArrayList от LinkedList". Вас спросят про потокобезопасные коллекции и их внутреннее устройство.
Вопрос: Как работает ConcurrentHashMap?
В Java 8+ ConcurrentHashMap использует сегментирование с CAS-операциями (Compare-And-Swap) и синхронизацию только на уровне отдельных "корзин" (buckets) при коллизиях. Это позволяет нескольким потокам одновременно читать и писать данные без блокировки всей таблицы.
Ключевые особенности:
- Чтение (
get) — полностью без блокировок. - Запись (
put) — используетsynchronizedтолько на конкретном узле списка/дерева. - Не допускает
nullни для ключей, ни для значений.
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Безопасное добавление
map.put("key1", 1);
// Атомарная операция: если ключ отсутствует, вставить значение
map.putIfAbsent("key2", 2);
// Атомарное обновление
map.compute("key1", (k, v) -> v == null ? 1 : v + 1);
System.out.println(map.get("key1")); // 2
}
}
Вопрос: Когда использовать WeakHashMap?
WeakHashMap использует слабые ссылки (WeakReference) для ключей. Если на ключ больше нет сильных ссылок в приложении, сборщи