Quick Java Thread Overview
Consider this a small but very necessary diversion from our discussion of the Quartz framework. Threads play an important role in the Quartz frameworkfor that matter, in Java in general. Without threads, Java (and, subsequently, Quartz) would have to use heavy-weight processes to perform simultaneous tasks (jobs, in the Quartz vernacular). This material might be basic for those who understand how threads work in Java. If you do, bear with us. If you haven't yet had the opportunity to learn about Java threads, this is a perfect time for a quick overview. Although the discussion focuses on Java threads in general, we tie it all together at the end with a discussion on how threads are used in Quartz.
Threads in Java
Threads allow a program to do many tasks simultaneouslyor, at least, it seems that the tasks are running simultaneously. Excluding parallel processing for this discussion, only a single thread executes at any particular moment, but the CPU gives each thread a little bit of time to run and then switches (through time-slicing) back and forth between threads very quickly. It can give the appearance of multiple running threads.
The Java language includes built-in support for threads with the Thread class. When a thread is told to run, the thread's run() method is executed. Just because you create a thread instance and call the start() method does not mean that the run() method will be executed immediately; the thread instance must wait until the JVM tells it that it can run.
Life Cycle of a Thread
A thread can be in one of several possible states during its life-time. It can be in only one state at a time, and these states are dictated by the JVM, not the operating system. The thread states are listed here:
- New
- Runnable
- Blocked
- Waiting
- Timed Waiting
- Terminated
When a new thread is created, it is assigned to the New state. No system resources are allocated to a thread in this state. To move the thread to the Runnable state, the start() method must be called.
When the start() method is called on a new thread, the thread enters the Runnable state and is considered to be running. This doesn't mean that the thread is actually executing instructions. Even though the state is set to Runnable and the thread is scheduled to run, it might have to wait until other executing threads finish or are temporarily suspended. The JVM decides which thread should get a chance to run next. The manner in which the JVM determines the next thread depends on several factors, including the operating system, the particular JVM, and thread priority, among others. Figure 4.4 shows the life cycle of a Java thread.
Figure 4.4. The life cycle of a Java thread
The Blocked and Waiting states are a little beyond the discussion for this book and tie into some topics that we don't need to explore here. If you want more information on this topic, see the Java tutorial on threads at the Sun site, http://java.sun.com/docs/books/tutorial/essential/threads/index.html.
Processes Versus Threads
Each Java program is assigned a process. The program then breaks up its tasks into threads. Even when you write a simple "Hello, World" program that consists of only a single main() method, the program still uses threads, albeit just one.
Sometimes the term lightweight process is used to refer to a thread. This is in contrast to the term heavyweight, which refers to an actual operating system process. If you are using the Windows operating system, you can look at the Task Manager and see the heavyweight processes running, but you won't see threads. Figure 4.5 illustrates how multiple threads operate within a Java application.
Figure 4.5. Two threads running within a program
As you can see from Figure 4.5, threads don't usually run concurrently. Putting aside multiple CPUs and parallel processing here, only one thread at a time gets attention from the CPU to execute. Each JVM and operating system might handle this differently, however. Sometimes the thread might get to run to completion and then another thread can then run. This is known as non-preemptive. Other times, a thread is interrupted so that the others can do their thing. This is called preemptive. In general, threads must compete with one another for the CPU, and this competition is often based on thread priority.
Understanding and Using Thread Priorities
We have mentioned several times that it's up to the JVM to determine which Runnable thread should get the next opportunity to run. The JVM supports a scheduling scheme known as fixed-priority scheduling that schedules threads based on their priority, compared to other Runnable threads.
The priority is an integer value that gets assigned to a new thread based on the priority of the parent thread that created it. The value ranges from Thread.MIN_PRIORITY, which equates to 1, up to Thread.MAX_PRIORITY, which equates to 10. The constants are defined in the java.lang.Thread class.
The larger the integer value (up to the MAX_PRIORITY), the higher the priority the thread will have. Unless otherwise specified, most of the threads created in a Java application run at Thread.NORMAL_PRIORITY, which is right in the middle at a value of 5. You can modify the priority of the thread by calling the setPriority() method.
In general, a running thread continues to run until one of several conditions occurs:
- The thread yields (possibly by calling the sleep() method, yield() method, or Object.wait() method).
- The thread's run() method finishes.
- If the OS supports time slicing, its time is up.
- Another higher-priority thread becomes Runnable.
Daemon Threads
Daemon threads are sometimes referred to as service threads becomes they usually run at a very low priority (in the background) and do the dull yet essential tasks. For example, the garbage collector in Java is an example of a daemon thread. This thread runs in the background and looks for and reclaims memory that the application no longer is using.
You can make a thread a daemon by setting true in the setDaemon() method. Otherwise, the thread will be a user thread. You can make a thread a daemon thread only before the thread is started. Daemon threads are special, in that the JVM exits if only daemon threads are running and no nondaemon threads are still alive.
Java ThreadGroups and ThreadPools
THReadGroups in Java are implemented by the java.lang.ThreadGroup class and represent a group of threads that can be acted upon as a single entity. Every Java thread is a member of a thread group. In a thread group, you can stop and start all of the threads that are members of the group. You can set priorities and perform other common thread functions. Thread groups are very useful for building multithreaded applications such as Quartz.
When a Java application starts, it creates a THReadGroup called main. Unless specified, all threads that are created become members of this group. When a thread is assigned to a specific THReadGroup, it can't be changed.
Java ThreadPools
Java 1.5 introduced a new concept to the Java language called thread pools. At first glance, these might seem similar to thread groups, but they actually are used for different purposes. Whereas multiple threads can belong to the same ThreadGroup, groups don't share the qualities of a typical pooled resource. That is, thread groups are only used for membership.
Thread pools are shared resources and are a managed collection of threads that can be used to perform tasks. Thread pools (and resource pools, in general) provide several benefits over nonpooled resources. First and foremost, resource pools provide performance improvements by reducing the overhead of object creation. If you instantiate ten threads and use these threads repeatedly instead of creating a new one every time you need one, you improve the performance of the application. This concept permeates Java and J2EE. Other benefits include being able to limit the number of resources, which can help the stability and scalability of the applicationa greatly desired feature, no matter what language or application this is.
Thread Usage in Quartz
|