This content originally appeared on Level Up Coding - Medium and was authored by Rahul Arora
Learn how tasks are scheduled by the ScheduledExecutorService.
Java’s ScheduledExecutorService is the ultimate go-to solution when it comes to running task threads periodically or with a fixed (including a delay of 0) interval.
But, have you ever wondered how this all works internally? Like how are different race conditions solved? Or how are tasks delayed for some time and picked up right when they are required?
In this article, we delve into the internal mechanisms of this exceptional library. We explore the underlying data structures that support its functionality, uncover the strategies employed to handle race conditions, and examine the seamless coordination among multiple internal classes that ensures smooth operation.
This will be really interesting so, without further ado, let’s begin.
Tell Me The Basics First
In this section, we’ll take a brief look at the essential classes that contribute to understanding theScheduledExecutorService and how they are interconnected.
The diagram given below shows three significant components. An Executor, ThreadPoolExecutor and ScheduledThreadPoolExecutor. Now, let’s try to get a gist of these components.
Executors
This class contains all the factory and utility methods that are required to create or manage thread pools. You would have used it at some point to create one yourself. An example code snippet of how it is used is given below:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
This code snippet will internally create a ScheduledThreadPoolExecutor of 10 threads. Also, the code given above creates a ScheduledThreadPoolExecutor, but it can be used to create a variety of different thread pools.
ThreadPoolExecutor
At the core of this library, the ThreadPoolExecutor class performs various vital functions, including configuring and managing the thread pool, handling task execution, and maintaining necessary bookkeeping.
If you closely observed the diagram mentioned earlier, you would have noticed two significant sub-components within the ThreadPoolExecutor.
Worker
The first one is the Worker, a nested class defined within the ThreadPoolExecutor. When we refer to a thread pool, we implicitly refer to these workers. It is the responsibility of the ThreadPoolExecutor to manage this worker pool efficiently. This includes adding workers, running workers, interrupting workers, etc.
Whenever a client submits a task to the ExecutorService, these are picked up by one of the worker threads and executed.
BlockingQueue
The second crucial attribute is BlockingQueue. By default, the work queue employed within the ThreadPoolExecutor is a BlockingQueue. Its primary function is to receive tasks from the client thread and distribute them among the worker threads in a thread-safe manner.
ScheduledThreadPoolExecutor
The ScheduledThreadPoolExecutor class is an extension of ThreadPoolExecutor. It leverages various methods from its parent class while introducing a single modification: the work queue definition. Instead of a simple BlockingQueue, it utilizes an extended version known as DelayedWorkQueue.
This specialized data structure lies at the heart of the scheduling mechanism. We will go through it in detail in the next section.
Having understood the various classes involved, let’s dive deeper into their intricate details.
Let’s Focus On The Core
When a task is submitted to the ScheduledExecutorService, it gets added to the DelayedWorkQueue.
This queue is continuously polled by the available worker threads. Once a task is detected in the queue, the worker threads contend for a lock to remove the task. This locking mechanism ensures that multiple workers do not attempt to process the same task simultaneously.
Now, here’s the intriguing part: the work queue utilized by ScheduledExecutorService is not a typical queue; it's a min-heap. Tasks within this heap are sorted based on their scheduled execution delay. This means a task scheduled at time T0 will be positioned above a task scheduled at time T1, given that T1 > T0.
So, when the worker threads poll the queue, they also check whether the task at the head of the queue is ready for execution or not based on the current timestamp and the timestamp at which the task was supposed to run.
Once a task becomes eligible, it is extracted from the queue and executed promptly. An example code snippet to schedule a task with a fixed delay of 5 seconds is given below:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
}, 5, TimeUnit.SECONDS);
The explanation provided above effectively covers the scenario of a one-time scheduled task. Now, let’s consider recurring tasks and instant tasks, which are handled similarly.
Recurring Tasks
After a recurring task completes its execution for the first time, it is reintroduced into the work queue with the given delay. This cycle continues until the task is explicitly canceled by the client.
An example code snippet to run a recurring task with an initial delay of 0 seconds and then a periodic interval of 5 seconds is given below:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
}, 0, 5, TimeUnit.SECONDS);
Instant Tasks
Instant tasks are a subset of tasks with a fixed delay. In this case, however, the fixed delay is set to 0. Thus, instant tasks are executed immediately upon being scheduled. An example code snippet to run the task instantly is given below:
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.submit(new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
});
In this article, we have explored the internal mechanisms of the ScheduledExecutorService without delving into unnecessary complexity. The elegance of its implementation within Java is truly remarkable. By comprehending its internals, you can enhance your skills as a developer, as the principles learned can be applied to various scenarios that involve the utilization of a delay queue.
Thank you for reading.
Did you know that you can clap up to 50 times on a story? Don’t forget to clap and share if you like this article. Happy coding!
Level Up Coding
Thanks for being a part of our community! Before you go:
- 👏 Clap for the story and follow the author 👉
- 📰 View more content in the Level Up Coding publication
- 💰 Free coding interview course ⇒ View Course
- 🔔 Follow us: Twitter | LinkedIn | Newsletter
🚀👉 Join the Level Up talent collective and find an amazing job
Dive Deep Into ScheduledExecutorService’s Internals was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding - Medium and was authored by Rahul Arora
Rahul Arora | Sciencx (2023-06-04T19:36:51+00:00) Dive Deep Into ScheduledExecutorService’s Internals. Retrieved from https://www.scien.cx/2023/06/04/dive-deep-into-scheduledexecutorservices-internals/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.