Class 27: Introduction to Executor and ExecutorService
Part 5 — Managing Threads with the Executor Framework
In the previous class, we understood why creating and managing every thread manually can become difficult.
We also learned that the Executor Framework helps separate the task we want to perform from the threads that execute it.
Now let's understand the two important interfaces that we use to work with this framework:
ExecutorExecutorService
1. First, What Is an Interface?
Before understanding these two interfaces, let's quickly revise what an interface means in Java.
An interface defines a set of methods that a class can implement. It tells us what operations are available, without necessarily telling us exactly how those operations are performed.
Think of a television remote.
The remote provides buttons such as Power, Volume Up and Volume Down. You know what each button does, but you do not need to understand the internal electronics every time you press one.
Similarly, Java interfaces define operations that we can use. The implementation handles the actual work.
2. Understanding the Executor Interface
The Executor interface is one of the simplest parts
of the Executor Framework.
Its main responsibility is to execute a task.
It provides one important method:
void execute(Runnable command);
Let's understand its parts:
execute()is the method we call to submit a task.Runnablerepresents the work that needs to be performed.- The Executor decides how that task will be executed.
Notice something important: we are not creating a Thread object
or calling start() ourselves.
We simply give the task to the Executor.
3. Our First Executor Example
Let's look at a simple example.
Java provides a class called DirectExecutor?
No. We can create our own simple implementation of the
Executor interface to understand how it works.
import java.util.concurrent.Executor;
class MyExecutor implements Executor {
@Override
public void execute(Runnable command) {
command.run();
}
}
public class ExecutorExample {
public static void main(String[] args) {
Executor executor = new MyExecutor();
executor.execute(() -> {
System.out.println("Task is running");
});
System.out.println("Main method completed");
}
}
Let's understand this program step by step.
Step 1: Implement Executor
class MyExecutor implements Executor
We create our own class and implement the Executor interface.
Step 2: Implement execute()
public void execute(Runnable command) {
command.run();
}
We receive a Runnable task and call its run() method.
This implementation executes the task directly on the thread
that calls execute().
In this example, that means the task runs on the main thread. We have implemented the interface, but we have not created a separate thread.
Step 3: Submit a Task
Executor executor = new MyExecutor();
executor.execute(() -> {
System.out.println("Task is running");
});
We create an Executor reference and submit a Runnable task.
The Executor implementation decides how to execute that task.
This is an important idea: the Executor interface defines the operation, while its implementation decides how that operation is carried out.
4. Understanding ExecutorService
The Executor interface is useful, but it provides only a basic task execution operation.
In real applications, we often need additional capabilities.
For example:
- Submit tasks and receive results.
- Manage the lifecycle of the executor.
- Stop accepting new tasks.
- Wait for submitted tasks to finish.
This is where ExecutorService comes in.
ExecutorService extends the Executor
interface and provides additional methods for managing task
execution and the executor's lifecycle.
In simple words:
- Executor: Provides a basic way to execute tasks.
- ExecutorService: Provides task execution along with additional management capabilities.
5. Creating an ExecutorService
Java provides ready-made implementations that we can use.
One common way to obtain an ExecutorService is through the
Executors utility class.
Let's look at a simple example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceExample {
public static void main(String[] args) {
ExecutorService executorService =
Executors.newSingleThreadExecutor();
executorService.execute(() -> {
System.out.println("Task is running");
});
executorService.shutdown();
}
}
Let's understand what is happening.
Step 1: Create an ExecutorService
ExecutorService executorService =
Executors.newSingleThreadExecutor();
We ask Java to provide an ExecutorService that uses a single worker thread.
We will understand how this works in more detail when we study thread pools in the next class.
Step 2: Submit a Task
executorService.execute(() -> {
System.out.println("Task is running");
});
We submit our task using the execute() method.
Unlike our earlier custom Executor, this ExecutorService uses its managed worker thread to execute the task.
Step 3: Shut Down the ExecutorService
executorService.shutdown();
Once we have submitted the work, we should also manage the executor's lifecycle.
Calling shutdown() tells the ExecutorService that
we do not want to submit any more tasks.
It allows already submitted tasks to complete. It does not immediately stop those tasks.
We will study proper shutdown in detail in Class 29.
6. Executor vs. ExecutorService
Let's compare these two interfaces.
| Executor | ExecutorService |
|---|---|
| Provides basic task execution. | Provides task execution and additional management features. |
| Has the execute() method. | Inherits execute() and provides additional methods. |
| Does not define lifecycle management methods. | Provides methods such as shutdown() and shutdownNow(). |
| Does not provide submit(). | Provides submit() for submitting tasks and receiving Future objects. |
Remember that ExecutorService extends
Executor. Therefore, every ExecutorService can also
be used wherever an Executor is expected.
7. A Few Important Methods of ExecutorService
We will study these methods in detail in upcoming classes. For now, let's simply understand what they do.
| Method | Purpose |
|---|---|
execute() |
Submits a Runnable task for execution. |
submit() |
Submits a task and returns a Future representing its execution. |
shutdown() |
Stops accepting new tasks while allowing submitted tasks to finish. |
shutdownNow() |
Attempts to stop active tasks and returns tasks that have not started. |
isShutdown() |
Checks whether shutdown has been initiated. |
isTerminated() |
Checks whether all tasks have completed after shutdown. |
We don't need to memorize all these methods right now. We will use them gradually as we move forward.
8. One Important Thing to Remember
The Executor Framework does not mean that we never need to understand threads again.
We still need to understand how threads work, how shared data is protected, and how tasks communicate with each other.
The framework simply makes it easier to organize and manage the execution of tasks.
Think of it this way:
Earlier, we were responsible for creating and starting threads ourselves. Now, we can focus on submitting tasks and let an executor manage their execution.
In the next class, we will understand how multiple tasks can share a group of reusable threads. This is called a Thread Pool.
Test Your Understanding
Let's see how well you understood this class!
Question 1: What is the main responsibility of the Executor interface?
Question 2: Which method is defined by the Executor interface?
Question 3: What is the relationship between Executor and ExecutorService?
Question 4: What does shutdown() do?
Question 5: Which class provides factory methods such as newSingleThreadExecutor()?
Class Summary
In this class, we learned about two important interfaces in the Executor Framework.
Executorprovides a basic way to execute Runnable tasks.- Its main method is
execute(). ExecutorServiceextends Executor.- ExecutorService provides additional features for submitting tasks and managing the executor lifecycle.
- The
Executorsutility class provides factory methods to create ExecutorService implementations. shutdown()stops accepting new tasks while allowing submitted tasks to finish.
Remember: Executor is about executing tasks. ExecutorService gives us additional control over task execution and lifecycle management.
You have completed 27 out of 46 classes.