Java ExecutorService

Introduction

ExecutorService in Java is a part of the java.util.concurrent package and provides a higher-level replacement for working with threads directly. It simplifies the process of managing a pool of threads and executing tasks asynchronously. ExecutorService offers various methods for submitting tasks, shutting down the executor, and managing the lifecycle of threads.

Key Points:

  • Thread Pool Management: Manages a pool of worker threads.
  • Task Submission: Provides methods to submit tasks for execution.
  • Lifecycle Management: Offers methods to gracefully shut down the executor service.
  • Concurrency Utilities: Part of the java.util.concurrent package, which provides robust concurrency utilities.

Table of Contents

  1. Creating an ExecutorService
  2. Submitting Tasks
  3. Shutting Down ExecutorService
  4. ScheduledExecutorService
  5. Example: Using ExecutorService
  6. Best Practices
  7. Real-World Analogy
  8. Conclusion

1. Creating an ExecutorService

Fixed Thread Pool

Creates a thread pool with a fixed number of threads.

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class FixedThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executor.execute(new Task()); } executor.shutdown(); } } class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the task."); } } 

Cached Thread Pool

Creates a thread pool that creates new threads as needed but will reuse previously constructed threads when they are available.

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class CachedThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { executor.execute(new Task()); } executor.shutdown(); } } class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the task."); } } 

Single Thread Executor

Creates an executor that uses a single worker thread operating off an unbounded queue.

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SingleThreadExecutorExample { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { executor.execute(new Task()); } executor.shutdown(); } } class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the task."); } } 

2. Submitting Tasks

Runnable Tasks

Tasks can be submitted to the executor using the execute() or submit() methods.

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class RunnableTaskExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { executor.execute(new Task()); } executor.shutdown(); } } class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the task."); } } 

Callable Tasks

Tasks that return a result can be submitted using the submit() method, which returns a Future.

import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ExecutionException; public class CallableTaskExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); for (int i = 0; i < 5; i++) { Future<Integer> result = executor.submit(new Task()); try { System.out.println("Result: " + result.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } executor.shutdown(); } } class Task implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 5; i++) { sum += i; } return sum; } } 

3. Shutting Down ExecutorService

Graceful Shutdown

Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

executor.shutdown(); 

Forceful Shutdown

Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution.

executor.shutdownNow(); 

Await Termination

Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } 

4. ScheduledExecutorService

ScheduledExecutorService is an ExecutorService that can schedule commands to run after a given delay, or to execute periodically.

Example:

import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecutorServiceExample { public static void main(String[] args) { ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3); scheduler.scheduleAtFixedRate(new Task(), 0, 2, TimeUnit.SECONDS); try { Thread.sleep(10000); // Run for 10 seconds } catch (InterruptedException e) { e.printStackTrace(); } scheduler.shutdown(); } } class Task implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the task."); } } 

5. Example: Using ExecutorService

Comprehensive Example

import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class ExecutorServiceExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Submitting Runnable tasks for (int i = 0; i < 5; i++) { executor.execute(new RunnableTask()); } // Submitting Callable tasks for (int i = 0; i < 5; i++) { Future<Integer> result = executor.submit(new CallableTask()); try { System.out.println("Result: " + result.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } // Shutting down the executor executor.shutdown(); try { if (!executor.awaitTermination(60, TimeUnit.SECONDS)) { executor.shutdownNow(); } } catch (InterruptedException e) { executor.shutdownNow(); Thread.currentThread().interrupt(); } } } class RunnableTask implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " is executing the Runnable task."); } } class CallableTask implements Callable<Integer> { @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i < 5; i++) { sum += i; } return sum; } } 

Output:

pool-1-thread-1 is executing the Runnable task. pool-1-thread-2 is executing the Runnable task. pool-1-thread-3 is executing the Runnable task. pool-1-thread-1 is executing the Runnable task. pool-1-thread-2 is executing the Runnable task. Result: 10 Result: 10 Result: 10 Result: 10 Result: 10 

6. Best Practices

  1. Use Thread Pools: Use ExecutorService to manage thread pools efficiently.
  2. Properly Shutdown Executors: Always shut down executors to release resources.
  3. Handle Exceptions: Properly handle exceptions in tasks and during shutdown.
  4. Avoid Task Submission After Shutdown: Avoid submitting new tasks after calling shutdown().
  5. Use ScheduledExecutorService for Scheduling: Use ScheduledExecutorService for periodic or delayed task execution.

7. Real-World Analogy

Consider a restaurant kitchen where:

  • Thread Pool: The kitchen staff (threads) are part of a team that prepares meals.
  • Task Submission: Orders (tasks) are submitted to the kitchen staff.
  • Lifecycle Management: The kitchen opens (starts) and closes (shuts down) at specified times.
  • Scheduled Tasks: Some meals are prepared at regular intervals or need to be checked periodically.

8. Conclusion

ExecutorService in Java provides a high-level API for managing a pool of threads and executing tasks asynchronously. It simplifies thread management and offers methods for submitting tasks, shutting down executors, and scheduling tasks.

Leave a Comment

Scroll to Top