The JavaTM Tutorial
Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Trail: Essential Java Classes
Lesson: Threads: Doing Two or More Tasks at Once

Thread Scheduling

As mentioned briefly in the previous section, many computer configurations have a single CPU. Hence, threads run one at a time in such a way as to provide an illusion of concurrency. Execution of multiple threads on a single CPU in some order is called scheduling. The Java runtime environment supports a very simple, deterministic algorithm called fixed-priority scheduling. This algorithm schedules threads on the basis of their priority relative to other Runnable threads.

When a thread is created, it inherits its priority from the thread that created it. In addition, by using the setPriority method, you can modify a thread's priority at any time after its creation. Thread priorities are integers that range between MIN_PRIORITY and MAX_PRIORITY (constants defined in the Thread class). The higher the integer, the higher the priority. At any given time, when multiple threads are ready to be executed, the runtime system chooses the Runnable thread for execution that has the highest priority. Only when that thread stops, yields, or becomes Not Runnable will a lower-priority thread start executing. If two threads of the same priority are waiting for the CPU, the scheduler arbitrarily chooses one of them to run first. The chosen thread runs until one of the following conditions is true:

Then the second thread is given a chance to run, and so on, until the interpreter exits.

The Java runtime system's thread-scheduling algorithm is also preemptive. If at any time a thread with a higher priority than all other Runnable threads becomes Runnable, the runtime system chooses the new higher-priority thread for execution. The new thread is said to preempt the other threads.


Rule of Thumb: At any given time, the highest-priority thread is running. However, this is not guaranteed. The thread scheduler may choose to run a lower-priority thread to avoid starvation. For this reason, use thread priority only to affect scheduling policy for efficiency purposes; do not rely on it for algorithm correctness.

A Thread Race

RaceApplet (in a .java source file) is an applet that animates a race between two "runner" threads of different priorities. Clicking the mouse on the applet starts the two runners. Runner 2 has a priority of 2; runner 3 has a priority of 3.

Try This: Click the applet below to start the race.

Note: If you don't see the applet running above, you need to install Java Plug-in, which happens automatically when you install the J2SE JRE or JDK. This applet requires JDK 5.0 or later. You can find more information in the Java Plug-in home page.

The runners are implemented by a Thread subclass called Runner. The following is the run method for the Runner class, which simply counts from 1 to 10,000,000.

public int tick = 1;
public void run() {
    while (tick < 10000000) {
        tick++;
    }
} 
This applet has a third thread, which handles the drawing. The drawing thread's run method loops until the applet stops. During each iteration of the loop, the thread draws a line for each runner, the length of which is computed from the runner's tick variable; the thread then sleeps for 10 milliseconds. The drawing thread has a thread priority of 4 — higher than that of either runner. Thus, whenever the drawing thread wakes up after 10 milliseconds, it becomes the highest-priority thread, preempting whichever runner is currently running, and draws the lines. You can see that the lines inch their way across the page.

This is not a fair race because one runner has a higher priority than the other. Each time the drawing thread yields the CPU by going to sleep for 10 milliseconds, the scheduler chooses the highest-priority Runnable thread to run; in this case, it's always runner 3.

Here is another applet, one that implements a fair race, in which both runners have the same priority and an equal chance of being chosen to run.


Try This: Click the applet below to start the race.

Note: If you don't see the applet running above, you need to install Java Plug-in, which happens automatically when you install the J2SE JRE or JDK. This applet requires JDK 5.0 or later. You can find more information in the Java Plug-in home page.
In this race, each time the drawing thread yields the CPU by going to sleep, there are two Runnable threads of equal priority — the runners — waiting for the CPU. The scheduler must choose one of the threads to run. In this case, the scheduler arbitrarily chooses one.

Selfish Threads

The Runner class used in the previous races implements impaired thread behavior. Recall the code for the run method from the Runner class used in the races.
public int tick = 1;
public void run() {
    while (tick < 10000000) {
        tick++;
    }
} 
The while loop in the run method is in a tight loop. Once the scheduler chooses a thread with this thread body for execution, the thread never voluntarily relinquishes control of the CPU; it just continues to run until the while loop terminates naturally or until the thread is preempted by a higher-priority thread. This thread is called a selfish thread.

In some cases, having selfish threads doesn't cause any problems because a higher-priority thread preempts the selfish one, just as the drawing thread in RaceApplet preempts the selfish runners. However, in other cases, threads with CPU-greedy run methods can take over the CPU and cause other threads to wait for a long time, even forever, before getting a chance to run.

Time-Slicing

Some systems limit selfish-thread behavior with a strategy known as time slicing. Time slicing comes into play when multiple Runnable threads of equal priority are the highest-priority threads competing for the CPU. For example, a standalone program, RaceTest.java (in a .java source file), based on RaceApplet (in a .java source file) creates two equal-priority selfish threads that have the following run method.
public void run() {
    while (tick < 400000) {
        tick++;
        if ((tick % 50000) == 0) {
            System.out.format("Thread #%d, tick = %d%n", num, tick);
        }
    }
}
This run method contains a tight loop that increments the integer tick. Every 50,000 ticks prints out the thread's identifier and its tick count.

When running this program on a time-sliced system, you will see messages from both threads intermingled, something like this.

Thread #1, tick = 50000
Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #1, tick = 250000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 400000
This output is produced because a time-sliced system divides the CPU into time slots and gives each equal-and-highest priority thread a time slot in which to run. The time-sliced system iterates through the equal-and-highest priority threads, allowing each one a bit of time to run, until one or more finishes or until a higher-priority thread preempts them. Note that time slicing makes no guarantees as to how often or in what order threads are scheduled to run.

When running this program on a system that is not time sliced, you will see messages from one thread finish printing before the other thread ever gets a chance to print one message. The output will look something like this.

Thread #0, tick = 50000
Thread #0, tick = 100000
Thread #0, tick = 150000
Thread #0, tick = 200000
Thread #0, tick = 250000
Thread #0, tick = 300000
Thread #0, tick = 350000
Thread #0, tick = 400000
Thread #1, tick = 50000
Thread #1, tick = 100000
Thread #1, tick = 150000
Thread #1, tick = 200000
Thread #1, tick = 250000
Thread #1, tick = 300000
Thread #1, tick = 350000
Thread #1, tick = 400000
The reason for this is that a system that is not time sliced chooses one of the equal-and-highest priority threads to run and allows that thread to run until it relinquishes the CPU (by sleeping, yielding, or finishing its job) or until a higher-priority thread preempts it.


Purity Tip: The Java platform does not implement (and therefore does not guarantee) time slicing; however, some platforms do support time slicing. Your programs should not rely on time slicing because it can produce different results on different systems.

Relinquishing the CPU

As you can imagine, writing CPU-intensive code can have negative repercussions on other threads running in the same process. In general, try to write well-behaved threads that voluntarily relinquish the CPU periodically and give other threads an opportunity to run.

A thread can voluntarily yield the CPU by calling the yield method, which gives other threads of the same priority a chance to run. If no equal-priority threads are Runnable, yield is ignored.

Thread Scheduling Summary


Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Copyright 1995-2005 Sun Microsystems, Inc. All rights reserved.