Thread Usage in Quartz
Threads are extremely important to Quartz because Quartz is designed to support multiple Jobs running simultaneously. To perform this feat, Quartz relies heavily on the thread support built into the Java language and augments it with a few of its own classes and interfaces. You have already seen some of this in the examples in this chapter and the previous one.
When a Quartz Scheduler is first created through one of the factory methods, the factory configures several important resources that the Scheduler will need throughout its lifetime. A few of those important resources have to do with threads.
The Main Processing Thread: QuartzSchedulerThread
When a Quartz application is first launched, the main thread starts the Scheduler. The QuartzScheduler is created and creates an instance of the org.quartz.core.QuartzSchedulerThread class. The QuartzSchedulerThread contains the processing loop for determining when the next job should be triggered. As the name implies, the QuartzSchedulerThread is a Java thread. It runs as a nondaemon thread with a normal priority.
The main processing loop for the QuartzSchedulerThread method is illustrated here:
1. |
While the scheduler is running: |
||
A. |
Check to see if standby mode has been requested. |
||
1. |
If the standby method has been called, wait for the signal to continue. |
||
B. |
Ask the JobStore for the next trigger to fire. |
||
1. |
If no trigger is ready to fire, wait for a small amount of time check again. |
||
2. |
If there's a trigger available, wait for the exact time for it to fire. |
||
D. |
When it's time, get the triggerFiredBundle for the trigger. |
||
E. |
Create a JobRunShell instance for the job using the Scheduler and triggerFiredBundle. |
||
F. |
Tell the ThreadPool to run the JobRunShell when possible. |
This logic exists within the run() method of the QuartzSchedulerThread.
The QuartzSchedulerResources
When the factory creates the Scheduler instance, it also passes the Scheduler an instance of an org.quartz.core.QuartzSchedulerResources. The QuartzSchedulerResources contains, among others things, a ThreadPool instance that provides a pool of worker threads that are responsible for executing jobs. In Quartz, a ThreadPool is represented by the org.quartz.spi.ThreadPool interface (because Quartz was invented before JDK 1.5, it needed to create its own ThreadPool classto ensure backward compatibility, Quartz still utilizes its THReadPool instead of Java's) and includes a concrete implementation called org.quartz.simpl.SimpleThreadPool. The SimpleThreadPool has a fixed number of worker threads and doesn't shrink or grow based on load. Figure 4.6 shows the sequence of steps that the framework undergoes during startup as it relates to threads.
Figure 4.6. Several thread-related resources are created at Quartz startup.
What Are Quartz Worker Threads?
Quartz doesn't process your jobs on the main thread. If it did, it would severely reduce the scalability of the application. Instead, Quartz delegates the thread-management responsibilities to a separate component. For the generic Quartz setup, which most users employ, the SimpleThreadPool class handles thread management. The SimpleThreadPool creates a number of WorkerThread instances that are responsible for processing a job on a separate thread. The WorkerThread is an inner class defined within the SimpleThreadPool class and is essentially a thread. The number of WorkerThreads that are created and the priority for these threads are specified by the configuration settings in the quartz.properties file or are passed into the factory.
When the QuartzSchedulerThread asks the ThreadPool to run a JobRunShell instance, the ThreadPool checks to see if a worker thread is available. If all the configured worker threads are busy, the ThreadPool waits until one becomes available. When a worker thread is available and a JobRunShell is waiting to be executed, the worker thread calls the run() method on the JobRunShell class.
The JobRunShell run() Method
Although WorkerThreads are indeed Java threads, the JobRunShell class implements Runnable as well. That means that it can act as a thread and contains a run() method. As discussed earlier in the chapter, the purpose of the JobRunShell is to call the execute() method on a job. It does this and also notifies the job and trigger listeners, and it finishes by updating trigger information about the execution.