The Java Executor Framework, introduced in Java 5, provides a powerful and flexible mechanism for managing threads and tasks in concurrent programming. It abstracts the thread management process and provides high-level constructs to simplify the development of concurrent applications.
The Java Executor Framework consists of several key interfaces and classes, primarily located in the java.util.concurrent
package. The main components include:
The Executor
interface provides a simple way to execute runnable tasks. It has a single method:
void execute(Runnable command);
The ExecutorService
interface extends Executor
and adds methods for managing the lifecycle of tasks and retrieving their results. Key methods include:
submit(Callable<T> task)
: Submits a callable task for execution and returns a Future
representing the task’s result.shutdown()
: Initiates an orderly shutdown of the executor service.invokeAll(Collection<? extends Callable<T>> tasks)
: Executes a collection of tasks and returns a list of Future
objects.invokeAny(Collection<? extends Callable<T>> tasks)
: Executes the given tasks and returns the result of the first successfully completed task.The ScheduledExecutorService
interface extends ExecutorService
and provides methods for scheduling tasks to execute after a delay or periodically.
Key methods include:
schedule(Runnable command, long delay, TimeUnit unit)
: Schedules a runnable task to execute after a given delay.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
: Schedules a task to run at a fixed rate.scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
: Schedules a task to run with a fixed delay between the end of one execution and the start of the next.The ThreadPoolExecutor
class is a powerful implementation of the ExecutorService
interface. It manages a pool of worker threads and provides methods to configure the number of threads and task queue management.
The ScheduledThreadPoolExecutor
class extends ThreadPoolExecutor
and implements the ScheduledExecutorService
interface, allowing for scheduled execution of tasks.
The Future
interface represents the result of an asynchronous computation. It provides methods to check if the computation is complete, retrieve the result, or cancel the task.
The Callable
interface is similar to Runnable
but can return a result and throw checked exceptions. It has a single method:
T call() throws Exception;
Let’s start with a simple example that demonstrates how to use the ExecutorService
to execute tasks concurrently.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
// Create a fixed thread pool with 3 threads
ExecutorService executor = Executors.newFixedThreadPool(3);
// Submit runnable tasks
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " is running on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // Simulating work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Shut down the executor
executor.shutdown();
}
}
Executors.newFixedThreadPool(3)
.executor.shutdown()
to prevent new tasks from being accepted.Now, let’s look at an example using Callable
and Future
to handle tasks that return results.
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;
public class CallableFutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(3);
// Create a callable task
Callable task = () -> {
int sum = 0;
for (int i = 1; i <= 5; i++) {
sum += i;
Thread.sleep(100); // Simulate some delay
}
return sum; // Return the sum of numbers
};
// Submit the task
Future future = executor.submit(task);
try {
// Retrieve the result of the computation
Integer result = future.get();
System.out.println("Result of the callable task: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
Callable
task that calculates the sum of the first five integers.Future
object.future.get()
to retrieve the result of the computation. This call blocks until the result is available.The ScheduledExecutorService
allows you to schedule tasks for future execution. Here’s how to use it.
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(1);
// Schedule a task to run after a 2-second delay
scheduler.schedule(() -> {
System.out.println("Task executed after 2 seconds delay");
}, 2, TimeUnit.SECONDS);
// Schedule a task to run every 1 second after an initial delay of 3 seconds
scheduler.scheduleAtFixedRate(() -> {
System.out.println("Periodic task executed: " + System.currentTimeMillis());
}, 3, 1, TimeUnit.SECONDS);
// Shut down the scheduler after some time
try {
Thread.sleep(10000); // Let the periodic task run for 10 seconds
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
scheduler.shutdown();
}
}
}
schedule()
.scheduleAtFixedRate()
.You can create a custom thread pool by instantiating ThreadPoolExecutor
directly, allowing you to fine-tune its behavior.
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class CustomThreadPoolExample {
public static void main(String[] args) {
// Create a custom thread pool with a core pool size of 2, maximum size of 4, and a 60-second keep-alive time
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2, 4, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()
);
// Submit tasks to the custom thread pool
for (int i = 0; i < 6; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Executing task " + taskId + " on thread " + Thread.currentThread().getName());
try {
Thread.sleep(1000); // Simulate work
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
// Shut down the executor
executor.shutdown();
}
}
ThreadPoolExecutor
with specific parameters for core pool size, maximum pool size, keep-alive time, and a task queue.The Executor Framework is well-suited for asynchronous programming, where you can execute tasks without blocking the main thread.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CompletableFutureExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
// Create a CompletableFuture for an asynchronous task
CompletableFuture future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000); // Simulate long-running task
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return 42; // Return the result
}, executor);
// Attach a callback to process the result when available
future.thenAccept(result -> {
System.out.println("The result is: " + result);
});
// Perform other tasks while waiting for the result
System.out.println("Doing other work...");
// Shut down the executor
executor.shutdown();
}
}
CompletableFuture
that runs a long-running task asynchronously using supplyAsync()
.thenAccept()
to process the result when it’s available.