Знакомы ли вы с многопоточностью в Java
Что проверяют
Формулировка «знакомы ли вы» обманчива: ответ «да» ничего не стоит, от вас ждут, что вы сами развернёте тему от основ до современных инструментов. Следом идут уточнения по модели памяти: зачем volatile, что такое happens-before, чем synchronized отличается от ReentrantLock.
Как отвечать
Идите слоями, на каждом одна-две конкретные вещи из практики.
- задачи и пулы:
RunnableиCallable, но в рабочем коде потоки руками не создают, а отдают задачи вExecutorService. Для цепочек асинхронных вызовов естьCompletableFutureсthenApply,thenCompose,exceptionally; - модель памяти: без синхронизации один поток может не увидеть запись другого.
volatileдаёт видимость и порядок, но не атомарность,synchronizedиLockдают и то и другое. В основе всего отношение happens-before; java.util.concurrent:AtomicIntegerиLongAdderдля счётчиков,ConcurrentHashMap,BlockingQueueдля схемы producer-consumer,CountDownLatchиSemaphoreдля координации;- виртуальные потоки: финальные с Java 21 (
Executors.newVirtualThreadPerTaskExecutor()). Дешёвые, их можно создавать сотнями тысяч под блокирующий ввод-вывод. Закрепление на платформенном потоке внутриsynchronizedустранено в Java 24; - проблемы: гонки, дедлоки, голодание. Дедлок лечится единым порядком захвата блокировок и
tryLockс таймаутом, а найти его помогает дамп потоков черезjstack.
Закончите примером из работы: что распараллеливали, какой пул выбрали и почему, какую ошибку ловили и как нашли.
Где спотыкаются
Считают, что volatile int counter решает гонку на counter++. Инкремент состоит из чтения и записи, нужен AtomicInteger или блокировка.
Вызывают run() вместо start() и удивляются, что код выполнился в текущем потоке. Или создают пул и забывают shutdown(), после чего приложение не завершается.
Переносят старые привычки на виртуальные потоки: складывают их в пул фиксированного размера или кладут тяжёлые объекты в ThreadLocal. Виртуальный поток создают на каждую задачу, а параллелизм ограничивают через Semaphore.