The ThreadPoolExecutor class allows us to send to the executor a list of tasks.
Then wait for the finalization of all the tasks in the list.
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; class Result { private String name; private int value; public String getName() { return name;/*from w w w . j av a2s. c o m*/ } public void setName(String name) { this.name = name; } public int getValue() { return value; } public void setValue(int value) { this.value = value; } } class Task implements Callable<Result> { private String name; public Task(String name) { this.name = name; } @Override public Result call() throws Exception { System.out.println("Staring "+ this.name); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } Result result = new Result(); result.setName(this.name); result.setValue(2); System.out.println("Ends "+ this.name); return result; } } public class Main { public static void main(String[] args) { ExecutorService executor = (ExecutorService) Executors.newCachedThreadPool(); List<Task> taskList = new ArrayList<>(); for (int i = 0; i < 3; i++) { Task task = new Task("Task-" + i); taskList.add(task); } List<Future<Result>> resultList = null; try { resultList = executor.invokeAll(taskList); } catch (InterruptedException e) { e.printStackTrace(); } executor.shutdown(); for (int i = 0; i < resultList.size(); i++) { Future<Result> future = resultList.get(i); try { Result result = future.get(); System.out.println(result.getName()+" "+ result.getValue()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } } }