В чем разница между многопоточностью и многопроцессорностью и когда что лучше использовать
Что проверяют
Понимаете ли вы разницу на уровне ОС и JVM: общая память против изоляции, цена переключения и обмена данными. Следом обычно спрашивают, как в Java организовать параллельные вычисления и что изменили виртуальные потоки.
Как отвечать
- поток: единица выполнения внутри процесса. Потоки одной JVM делят кучу, обмен данными дешёвый, но нужна синхронизация:
synchronized,volatile, атомики,java.util.concurrent. Порча общего состояния илиOutOfMemoryErrorзадевают всех; - процесс: отдельное адресное пространство, обмен только через IPC: сокеты, пайпы, файлы, брокер сообщений. Создание и переключение дороже, зато сбой изолирован;
- важная оговорка: потоки одного процесса в Java выполняются параллельно на разных ядрах. Глобальной блокировки интерпретатора, как в CPython, в JVM нет, поэтому для CPU-задач отдельные процессы в Java не нужны.
Когда что брать:
- вычисления внутри сервиса: пул потоков размером около числа ядер (
Runtime.getRuntime().availableProcessors()),ForkJoinPool, параллельные стримы; - I/O-нагрузка: много ожидания сети и базы. С Java 21 для этого есть виртуальные потоки (
Executors.newVirtualThreadPerTaskExecutor()): блокирующий код без пулов на тысячи платформенных потоков; - отдельные процессы: изоляция отказов и памяти, запуск внешней утилиты через
ProcessBuilder, горизонтальное масштабирование несколькими экземплярами сервиса за балансировщиком.
Где спотыкаются
Переносят логику Python: «для вычислений нужны процессы, потоки не дают параллелизма». Для Java это неверно, и интервьюер заметит это сразу.
Считают, что несколько процессов автоматически быстрее. Сериализация и копирование данных между ними часто съедают весь выигрыш, а общей кучи, которую можно просто читать, больше нет.
Кладут тяжёлые вычисления в виртуальные потоки или делают пул на сотни потоков для CPU-задач. Виртуальные потоки ускоряют ожидание, а не счёт, а до Java 24 долгие блоки synchronized ещё и закрепляли за собой поток-носитель.