1. Scheduled Task Execution with JUC Thread Pools
JUC's ScheduledExecutorService enables tasks to be executed at specific future times or with recurring delays. A common use case involves scheduling a task to run at a fixed point in time, such as every Thursday at 6:00 PM.
To achieve this, first determine the initial delay until the next target execusion time. If the current time has already passed the target time for the current week, schedule it for the following week. Subsequently, the task can repeat with a fixed delay representing one week.
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class WeeklyTaskScheduler {
public static void main(String[] args) {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
LocalDateTime now = LocalDateTime.now();
System.out.println("Current time: " + now);
// Determine the next Thursday at 18:00:00
LocalDateTime targetTime = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
// If the current time is past this week's Thursday, move to next week
if (now.compareTo(targetTime) > 0) {
targetTime = targetTime.plusWeeks(1);
}
long initialDelayMillis = Duration.between(now, targetTime).toMillis();
System.out.println("Next scheduled run: " + targetTime);
// Define the weekly period
long weeklyPeriodMillis = TimeUnit.DAYS.toMillis(7);
scheduler.scheduleWithFixedDelay(() -> {
System.out.println("Executing weekly task at: " + LocalDateTime.now());
}, initialDelayMillis, weeklyPeriodMillis, TimeUnit.MILLISECONDS);
}
}
2. Tomcat's Internal Thread Pool
Tomcat's architecture involves several components working together to handle incoming requests. A Server contains one or more Service instances, each comprising a Container (for managing servlets) and multiple Connectors (e.g., for HTTP and HTTPS). The Connector is crucial as it acts as the intefrace between the outside world and Tomcat, handling network communication (TCP/IP) and protocol parsing (HTTP).
2.1 Connector's NIO Endpoint
The Connector's NIO (Non-blocking I/O) Endpoint is responsible for efficient request handling. It typically includes:
- LimitLatch: A mechanism to control the maximum number of concurrent connections, similar to J.U.C.'s
Semaphore. - Acceptor: Dedicated thread(s) solely responsible for accepting new socket connections.
- Poller: Thread(s) that monitor connected socket channels for I/O events (e.g., data ready to be read). Upon detecting a readable event, the Poller wraps the socket into a
SocketProcessortask and submits it to anExecutorthread pool. - Executor: The primary thread pool where worker threads process the actual HTTP requests encapsulated within
SocketProcessortasks.
In essence, the Tomcat thread pool, particularly the Executor, is dedicated to processing detected I/O events and executing request logic.
2.2 Relationship with JUC ThreadPoolExecutor
Tomcat's internal thread pool (org.apache.tomcat.util.threads.ThreadPoolExecutor) extends and customizes the standard java.util.concurrent.ThreadPoolExecutor. While it inherits most of the base functionality, a key difference lies in its rejection policy.
Unlike the standard ThreadPoolExecutor, which immediately rejects a task when both the core threads are busy and the queue is full (or if maximumPoolSize is reached with a full queue), Tomcat's implementation attempts a different strategy. When maximumPoolSize is reached and a RejectedExecutionException would typically be thrown, Tomcat first tries to force the task into its work queue using a specialized TaskQueue. Only if this secondary attempt to enqueue also fails is a RejectedExecutionException finally propagated.
Consider the execute method's override:
public void execute(Runnable task, long timeout, TimeUnit unit) {
submittedTaskCount.incrementAndGet(); // Tracks active tasks
try {
super.execute(task); // Attempt standard execution
} catch (RejectedExecutionException rejectionException) {
// If standard execution fails, try to force into queue
if (super.getQueue() instanceof TaskQueue) {
final TaskQueue workQueue = (TaskQueue) super.getQueue();
try {
// Attempt to add task to queue, ignoring max pool size for now
if (!workQueue.force(task, timeout, unit)) {
submittedTaskCount.decrementAndGet();
throw new RejectedExecutionException("Work queue capacity is full.");
}
} catch (InterruptedException ie) {
submittedTaskCount.decrementAndGet();
Thread.interrupted();
throw new RejectedExecutionException(ie);
}
} else {
submittedTaskCount.decrementAndGet();
throw rejectionException;
}
}
}
// Inside TaskQueue.java
public boolean force(Runnable o, long timeout, TimeUnit unit) throws InterruptedException {
if (parent.isShutdown()) {
throw new RejectedExecutionException("Executor is not running, cannot force command into queue");
}
// This directly calls ArrayBlockingQueue's offer or LinkedBlockingQueue's offer
// bypassing ThreadPoolExecutor's check for maxPoolSize and queue capacity in some scenarios
return super.offer(o, timeout, unit);
}
This force method essentially tries to put the task onto the queue even if ThreadPoolExecutor might otherwise create a new