riven

Riven

Riven

Java executor framework with example

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. 

Introduction to the Executor Framework

Why Use the Executor Framework?

  1. Simplified Thread Management: The Executor Framework abstracts the complexities of thread management, allowing developers to focus on task execution rather than low-level thread handling.
  2. Thread Pooling: It allows for the reuse of threads, reducing the overhead associated with thread creation and destruction.
  3. Task Scheduling: The framework provides built-in support for scheduling tasks to run at fixed intervals or after a delay.
  4. Better Resource Management: By controlling the number of concurrent threads, the Executor Framework helps manage system resources more effectively.

Core Components of the Executor Framework

The Java Executor Framework consists of several key interfaces and classes, primarily located in the java.util.concurrent package. The main components include:

  1. Executor Interface
  2. ExecutorService Interface
  3. ScheduledExecutorService Interface
  4. ThreadPoolExecutor Class
  5. ScheduledThreadPoolExecutor Class
  6. Future Interface
  7. Callable Interface

1. Executor Interface

The Executor interface provides a simple way to execute runnable tasks. It has a single method:

				
					void execute(Runnable command);
				
			

2. ExecutorService Interface

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.

3. ScheduledExecutorService Interface

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.

4. ThreadPoolExecutor Class

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.

5. ScheduledThreadPoolExecutor Class

The ScheduledThreadPoolExecutor class extends ThreadPoolExecutor and implements the ScheduledExecutorService interface, allowing for scheduled execution of tasks.

6. Future Interface

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.

7. Callable Interface

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;
				
			

Using the Executor Framework

Basic Example: Using ExecutorService

Let’s start with a simple example that demonstrates how to use the ExecutorService to execute tasks concurrently.

Example: Basic ExecutorService

				
					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();
    }
}
				
			

Explanation

  • We create a fixed thread pool with three threads using Executors.newFixedThreadPool(3).
  • We submit five tasks to the executor, each printing a message and simulating work for one second.
  • Finally, we shut down the executor using executor.shutdown() to prevent new tasks from being accepted.

Advanced Example: Using Callable and Future

Now, let’s look at an example using Callable and Future to handle tasks that return results.

Example: Using Callable and Future

				
					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<Integer> 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<Integer> 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();
        }
    }
}
				
			

Explanation

  • We define a Callable task that calculates the sum of the first five integers.
  • We submit this task to the executor and obtain a Future object.
  • We use future.get() to retrieve the result of the computation. This call blocks until the result is available.
  • Finally, we shut down the executor.

Scheduling Tasks: Using ScheduledExecutorService

The ScheduledExecutorService allows you to schedule tasks for future execution. Here’s how to use it.

Example: Scheduling Tasks

				
					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();
        }
    }
}
				
			

Explanation

  • We create a scheduled executor service with a single thread.
  • We schedule a task to run after a 2-second delay using schedule().
  • We also schedule a periodic task to execute every second after an initial delay of 3 seconds using scheduleAtFixedRate().
  • Finally, we let the scheduler run for 10 seconds before shutting it down.

Advanced Features of the Executor Framework

Custom Thread Pool Configuration

You can create a custom thread pool by instantiating ThreadPoolExecutor directly, allowing you to fine-tune its behavior.

Example: Custom Thread Pool

				
					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();
    }
}
				
			

Explanation

  • We create a custom ThreadPoolExecutor with specific parameters for core pool size, maximum pool size, keep-alive time, and a task queue.
  • We submit six tasks to the executor, and the executor manages the number of active threads based on the defined configuration.

Using Executors for Asynchronous Programming

The Executor Framework is well-suited for asynchronous programming, where you can execute tasks without blocking the main thread.

Example: Asynchronous Execution with CompletableFuture

				
					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<Integer> 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();
    }
}
				
			

Explanation

  • We create a CompletableFuture that runs a long-running task asynchronously using supplyAsync().
  • We attach a callback with thenAccept() to process the result when it’s available.
  • Meanwhile, the main thread continues executing other tasks.
  • Finally, we shut down the executor.

Related topic

Java Database Connectivity (JDBC)
Java Database Connectivity with example Java Database Connectivity (JDBC) is an API that allows Java...
parallel stream in java
Parallel Stream in java Parallel streams automatically split the source data into multiple chunks and...
Java scanner user input
Java scanner for user input he Scanner class in Java is a powerful utility for reading input from various...
Java arraylist with example
Java arraylist with example In Java, the ArrayList class is part of the Java Collections Framework and...
Java synchronization
Java synchronizations What is Synchronizations? Synchronizations in Java is a technique used to control...
Set in java (Interface)
Set in java example In Java, the Set interface is part of the Java Collections Framework and represents...
Java Memory Management
Java Memory Management Java Memory Management is an essential aspect of Java programming that involves...
Jframe in java
What is JFrame in java? JFrame is a top-level container in Java Swing that represents a window on the...
Method overloading in java
Method Overloading in Java Method overloading is a fundamental concept in Java that allows a class to...
Model view controller
Model view controller(MVC) in spring Spring MVC is a part of the larger Spring Framework and is used...