January 8, 2023August 19, 2024 Java – Waiting for Running Threads to Finish Java concurrency allows running multiple sub-tasks of a task in separate threads. Sometimes, it is necessary to wait until all the threads have finished their execution. In this tutorial, we will learn a few ways to make the current thread wait for the other threads to finish. 1. Using ExecutorService and Future.get() Java ExecutorService (or ThreadPoolExecutor) helps execute Runnable or Callable tasks asynchronously. Its submit() method returns a Future object that we can use to cancel execution and/or wait for completion. In the following example, we have a demo Runnable task. Each task completes in a random time between 0 and 1 second. DemoRunnable.javapublic class DemoRunnable implements Runnable { private Integer jobNum; public DemoRunnable(Integer index) { this.jobNum = index; } @SneakyThrows @Override public void run() { Thread.sleep(new Random(0).nextLong(1000)); System.out.println("DemoRunnable completed for index : " + jobNum); } } We are submitting 10 tasks to the executor service. And then, we invoke Future.get() method on each Future object as received after submitting the task to the executor. The Future.get() waits if necessary for the task to complete, and then retrieves its result. ExecutorService executor = Executors.newFixedThreadPool(5); List<Future<?>> futures = new ArrayList<>(); for (int i = 1; i <= 10; i++) { Future<?> f = executor.submit(new DemoRunnable(i)); futures.add(f); } System.out.println("###### All tasks are submitted."); for (Future<?> f : futures) { f.get(); } System.out.println("###### All tasks are completed."); Output Output###### All tasks are submitted. DemoRunnable completed for index : 3 DemoRunnable completed for index : 4 DemoRunnable completed for index : 1 DemoRunnable completed for index : 5 DemoRunnable completed for index : 2 DemoRunnable completed for index : 6 DemoRunnable completed for index : 10 DemoRunnable completed for index : 7 DemoRunnable completed for index : 9 DemoRunnable completed for index : 8 ###### All tasks are completed. Note that the wait may terminate earlier under the following conditions: the task is cancelled the task execution threw an exception there is an InterruptedException i.e., the current thread was interrupted while waiting. In such a case, we should implement our own logic to handle the exception. 2. Using ExecutorService shutdown() and awaitTermination() The awaitTermination() method blocks until all tasks have completed execution after a shutdown() request on the executor service. Similar to Future.get(), it can unblock earlier if the timeout occurs, or the current thread is interrupted. The shutdown() method closes the executor, so no new tasks can be submitted, but previously submitted tasks continue execution. The following method has the complete logic of waiting for all tasks to finish in 1 minute. After that, the executor service will be shut down forcibly using shutdownNow() method. void shutdownAndAwaitTermination(ExecutorService executorService) { executorService.shutdown(); try { if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException ie) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } We can use this method as follows: ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 1; i <= 10; i++) { executor.submit(new DemoRunnable(i)); } System.out.println("###### All tasks are submitted."); shutdownAndAwaitTermination(executor); System.out.println("###### All tasks are completed."); 3. Using ExecutorService invokeAll() This approach can be seen as a combination of the previous two approaches. It accepts the tasks as a collection and returns a list of Future objects to retrieve output if necessary. Also, it uses the shutdown and awaits logic for waiting for the tasks to be complete. In the following example, we are using the DemoCallable class that is very similar to DemoRunnable, except it returns an Integer value. ExecutorService executor = Executors.newFixedThreadPool(10); List<DemoCallable> tasks = Arrays.asList( new DemoCallable(1), new DemoCallable(2), new DemoCallable(3), new DemoCallable(4), new DemoCallable(5), new DemoCallable(6), new DemoCallable(7), new DemoCallable(8), new DemoCallable(9), new DemoCallable(10)); System.out.println("###### Submitting all tasks."); List<Future<Integer>> listOfFutures = executor.invokeAll(tasks); shutdownAndAwaitTermination(executor); System.out.println("###### All tasks are completed."); Note that listOfFutures stores the task outputs in the same order in which we had submitted the tasks to the executor service. for (Future f : listOfFutures) { System.out.print(f.get() + " "); //Prints 1 2 3 4 5 6 7 8 9 10 } 4. Using CountDownLatch The CountDownLatch class enables a Java thread to wait until a collection of threads (latch is waiting for) to complete their tasks. CountDownLatch works by having a counter initialized with a number of threads, which is decremented each time a thread completes its execution. When the count reaches zero, it means all threads have completed their execution, and the main thread waiting on the latch resumes the execution. In the following example, the main thread is waiting for 3 given services to complete before reporting the final system status. We can read the whole example in CountDownLatch example. CountDownLatch latch = new CountDownLatch(3); List<BaseHealthChecker> services = new ArrayList<>(); services.add(new NetworkHealthChecker(latch)); services.add(new CacheHealthChecker(latch)); services.add(new DatabaseHealthChecker(latch)); Executor executor = Executors.newFixedThreadPool(services.size()); for(final BaseHealthChecker s : services) { executor.execute(s); } //Now wait till all health checks are complete latch.await(); Concurrency Java collectors class method java streamcountdownlatch and cyclicbarrier in javaexample of collectors class method java streamExecutorServiceexecutorservice tutorialshow to create thread in javahow to put thread in a wait statejava executorserviceuse of collectors class method java streamuse of tomap method java streamuse of toset method java streamwhere to use countdownlatchwhere to use cyclicbarrierwhy we use collectors class method java stream