Идиоматический способ достижения этого - использование Executor
в сочетании с CompletionService
.Это позволяет сопоставить многие единицы работы с пулом потоков фиксированного размера, а также предоставляет элегантный механизм блокировки до завершения всей работы.
Обратите внимание, что ваше беспокойство по поводу того, как использование пула потоков может повлиять на эффективность,на самом деле это не проблема: основные накладные расходы связаны с созданием отдельных потоков, которые вы все равно выполняете;дополнительные затраты на создание объекта при создании пула будут незначительными.
// Create fixed thread pool and wrap in a CompletionService to allow for easy access to completed tasks.
// We don't have an explicit result for each Runnable so parameterise the service on Void.
CompletionService<Void> cs = new ExecutorCompletionService<Void>(Executors.newFixedThreadPool(3));
// Create units of work for submission to completion service.
Runnable[] runnables = ...
// Submit runnables. Note that we don't care about the result so pass in null.
for (Runnable r : runnables) {
cs.submit(r, null);
}
// Take each *completed* result in turn, blocking until a completed result becomes available.
for (int i=0; i<runnables.length; ++i) {
Future<Void> completed = cs.take();
}