Приложение формирует независимые пакеты и передает Runnable или Callable через execute либо submit. ExecutorService — интерфейс; в ThreadPoolExecutor задания назначаются потокам или ожидают в очереди. Параллелизм ограничивают возможностями БД и пула соединений. Нужны контроль перегрузки, отдельные соединения для одновременных задач, обработка Future и ожидание завершения.
Как организовать параллельный bulk insert и откуда ExecutorService получает задания?
Разбиение вставки на пакеты, явная передача задач исполнителю, ограничение очереди и параллелизма, обработка ошибок и ожидание завершения.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Сначала оцените обычную пакетную вставку: дополнительные потоки не обязательно ускоряют базу данных. Если параллелизм оправдан, приложение разбивает входные данные на ограниченные пакеты и создает для них Runnable или Callable. Именно вызывающий код передает задания через execute или submit; исполнитель не извлекает их из базы автоматически.
ExecutorService — интерфейс, а не обещание конкретного устройства пула. В реализации ThreadPoolExecutor задачи передаются рабочим потокам или ожидают в очереди согласно настройкам. Для большого потока данных нужны ограниченная очередь и явная политика перегрузки: контролируемое замедление отправителя либо обработка отказа. Неограниченная очередь способна исчерпать память. Эти механизмы описаны в документации ThreadPoolExecutor.
Число одновременно выполняемых вставок согласуют с доступными соединениями и возможностями СУБД, оставляя ресурс другим запросам. Каждая одновременная задача должна использовать собственное соединение и согласованную транзакционную границу; делить одно JDBC-соединение между такими задачами не следует. Ресурсы закрывают, а ошибки транзакции обрабатывают явно. Несколько самостоятельных транзакций не обеспечивают атомарность всей загрузки.
После submit сохраняют Future и проверяют результат через get, обрабатывая исключения, таймауты и прерывание. Иначе завершившаяся с ошибкой задача может остаться незамеченной. Нужно также определить безопасный повтор пакета и поведение при частично выполненной загрузке.
shutdown() прекращает прием новых задач, но не ждет окончания прежних. Ожидание организуют через awaitTermination и результаты задач, контролируя исход. Различие закреплено в контракте ExecutorService.