#Use an Executor# Maintaining a work queue and a thread pool are problems that an [executor](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Executor.html) tackles: > This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. An Executor is normally used instead of explicitly creating threads. The use of `Future`s also helps you impose time constraints and handle exceptions thrown during execution. (Note that you'll typically want to use [`ExecutorService`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html) or [`CompletionService`](http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CompletionService.html) as `Executor` is a minimalistic interface.) To paraphrase your example: class IncrediblyLongRunningTask implements Callable<Long> { @Override public Long call() throws Exception { // ... } } public void compute() throws InterruptedException { final ExecutorService service = Executors.newFixedThreadPool(50); final List<IncrediblyLongRunningTask> tasks = new ArrayList<>(); for ( int i = 0; i < /*...*/; i++ ) { task.add(new IncrediblyLongRunningTask()); } final List<Future<Long>> futures = service.invokeAll(tasks); // returns when all done service.shutdown(); for ( final Future<Long> future : futures ) { // ... } }