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
- Creating an ExecutorService
- Submitting Tasks
- Shutting Down ExecutorService
- ScheduledExecutorService
- Example: Using ExecutorService
- Best Practices
- Real-World Analogy
- 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
- Use Thread Pools: Use
ExecutorService
to manage thread pools efficiently. - Properly Shutdown Executors: Always shut down executors to release resources.
- Handle Exceptions: Properly handle exceptions in tasks and during shutdown.
- Avoid Task Submission After Shutdown: Avoid submitting new tasks after calling
shutdown()
. - 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.