Master Java’s ScheduledThreadPoolExecutor: Fixed Rate, Fixed Delay & Leader‑Follower
This article explains Java's ScheduledThreadPoolExecutor, covering its two scheduling modes—fixed‑rate and fixed‑delay—its underlying DelayedWorkQueue implementation, and how it employs the Leader‑Follower pattern to efficiently manage delayed task execution.
ScheduledThreadPoolExecutor is a widely used Java tool for timed scheduling, offering two common modes: fixed‑rate execution and fixed‑delay execution, where the delay is measured from the end of the previous execution to the start of the next.
1.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
2.scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)It extends ThreadPoolExecutor and uses a custom DelayedWorkQueue to meet scheduling needs.
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS /*10L*/, MILLISECONDS,
new DelayedWorkQueue());
}DelayedWorkQueue is a min‑heap based queue that uses a ReentrantLock for thread‑safety; each ScheduledFutureTask stores a heapIndex to allow O(1) cancellation.
Leader‑Follower Mode
Unlike a regular thread pool, tasks submitted to a scheduled pool are delayed, so worker threads block on queue.take(). To avoid waking all threads when the next task’s delay expires, ScheduledThreadPoolExecutor adopts the Leader‑Follower pattern: one thread becomes the leader, waiting with awaitNanos until the head task’s scheduled time, while other threads remain followers, waiting indefinitely.
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0L)
return finishPoll(first);
first = null;
if (leader != null)
available.await();
else {
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}When a new element becomes the heap head, offer() clears the current leader and signals a waiting thread so that the leader‑follower cycle can continue.
public boolean offer(Runnable x) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// insert into queue
if (queue[0] == e) {
leader = null;
available.signal();
}
} finally {
lock.unlock();
}
return true;
}Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
