Chapter 6. Thread Prioritization

IN THIS CHAPTER

Java allows you to give each of the threads running in a virtual machine a priority. Higher- priority threads generally get more of a chance to run than lower-priority threads, but exact thread-scheduling behavior varies by VM implementation and operating system. Thread prioritization can be used to provide suggestions to the VM as to how you would like the threads to take turns running on the processor relative to each other, increasing an application's responsiveness to relatively more important events.

System Thread Priorities

Back in Chapter 3, "Creating and Starting a Thread," Table 3.1 listed the threads that are normally started by the VM for applications with a graphical user interface. Tables 6.1, 6.2, and 6.3 show the priorities that are assigned to each of these system threads for each major release of Sun's JDK.

Table 6.1. System Thread Priorities—JDK 1.2

Priority Thread Name
5 main
8 Finalizer
10 Reference Handler
5 Signal dispatcher
5 AWT-Windows
6 AWT-EventQueue-0
5 SunToolkit.PostEventQueue-0
4 Screen Updater

Table 6.2. System Thread Priorities—JDK 1.1

Priority Thread Name
5 main
1 Finalizer thread
5 AWT-Windows
5 AWT-EventQueue-0
4 Screen Updater

Table 6.3. System Thread Priorities—JDK 1.0

Priority Thread Name
5 main
1 Finalizer thread
5 AWT-Win32
5 AWT-Callback-Win32
4 Screen Updater

Chapter 3 provides descriptions of what these threads are used for and provides a cross-reference between the releases. The main thread is created by the VM and enters the main() method of applications. It runs at a middle-of-the-road priority of 5. The asynchronous garbage collection process uses the Finalizer thread. A notable change in JDK 1.2 is that the priority of the Finalizer thread was increased from 1 to 8 to help ensure that the garbage collector gets more of a chance to run and free up memory.

The AWT-EventQueue-0 thread is the event-handling thread. This thread invokes event-handling methods in response to GUI interactions with the user. In JDK 1.2, its priority is 6, just slighter higher than the priority of 5 that it had in JDK 1.1 and 1.0.

When assigning priorities to your threads, you should consider the relative priorities of the system threads to be sure your threads don't overwhelm their operations. By default, when a new Thread is constructed, it runs at the same priority as the thread that constructed it. Most new threads are constructed directly or indirectly by the main thread and will therefore run at a priority of 5. This works well under many scenarios, but there are times when you will want to raise or lower a thread's priority.

Thread Priority Constants

There are three public static final int member variables of Thread that indicate the range of priority values that can be passed to setPriority(). These constants should be used to determine the range of acceptable priorities.

Thread.MAX_PRIORITY

Thread.MAX_PRIORITY is the highest thread-scheduling priority that can be passed to setPriority() for a particular VM. Generally, it is 10. Threads running at this level might hog the processor and should be designed to block frequently to give other threads a chance to run.

Thread.MIN_PRIORITY

Thread.MIN_PRIORITY is the lowest thread-scheduling priority that can be passed to setPriority() for a particular VM. Generally, it is 1. Threads running at this priority might not get much processor time and might not get any if there are other higher-priority threads running.

Thread.NORM_PRIORITY

Thread.NORM_PRIORITY is a not-too-high, not-too-low thread-scheduling priority for setPriority(). Generally, it is 5. Threads running at this priority usually get a chance to run without hogging the processor.

Determining the Current Priority: getPriority()

The getPriority() method of Thread returns an int representing the current priority of the thread. Generally, a thread's priority does not change over its lifetime, but it can. The GetPriority class shown in Listing 6.1 demonstrates the use of getPriority().

Example 6.1. GetPriority.java—Using getPriority()

 1: public class GetPriority extends Object {
 2:     private static Runnable makeRunnable() {
 3:         Runnable r =  new Runnable() {
 4:                 public void run() {
 5:                     for ( int i = 0; i < 5; i++ ) {
 6:                         Thread t = Thread.currentThread();
 7:                         System.out.println(
 8:                             "in run() - priority=" +
 9:                             t.getPriority() +
10:                             ", name=" + t.getName());
11:
12:                         try {
13:                             Thread.sleep(2000);
14:                         } catch ( InterruptedException x ) {
15:                             // ignore
16:                         }
17:                     }
18:                 }
19:             } ;
20:
21:         return r;
22:     }
23:
24:     public static void main(String[] args) {
25:         System.out.println(
26:             "in main() - Thread.currentThread().getPriority()=" +
27:             Thread.currentThread().getPriority());
28:
29:         System.out.println(
30:             "in main() - Thread.currentThread().getName()=" +
31:             Thread.currentThread().getName());
32: 
33:         Thread threadA = new Thread(makeRunnable(), "threadA");
34:         threadA.start();
35:
36:         try { Thread.sleep(3000); } 
37:         catch ( InterruptedException x ) { }
38:
39:         System.out.println("in main() - threadA.getPriority()=" +
40:                 threadA.getPriority());
41:     }
42: }

In main(), the name and priority of the thread running main() is printed (lines 25–31). Next, makeRunnable() is used to create a new Runnable object to pass to the constructor of Thread (line 33). In addition, the name of this new thread is designated as threadA (line 33). Next, this thread is started (line 34) and is allowed to run for 3 seconds (line 36). After the sleep, getPriority() is used to determine the current priority of threadA (lines 39–40).

The makeRunnable() method (lines 2–22) is used to create a new Runnable instance that prints the name and priority of the thread running it five times. Each time through the loop, the current thread is determined (line 6), and then the name and priority are extracted and printed (lines 7–10). Before looping again, the thread sleeps for two seconds (lines 12–16).

Listing 6.2 shows the output produced when GetPriority is run. Your output should match.

Example 6.2. Output from GetPriority

in main() - Thread.currentThread().getPriority()=5
in main() - Thread.currentThread().getName()=main
in run() - priority=5, name=threadA
in run() - priority=5, name=threadA
in main() - threadA.getPriority()=5
in run() - priority=5, name=threadA
in run() - priority=5, name=threadA
in run() - priority=5, name=threadA

Notice that the name of the thread running main() is main and that its priority is 5. Also note that threadA inherited the priority of 5 from main.

Changing the Priority of a Thread: setPriority()

The setPriority() method of Thread takes an int as a parameter indicating the new priority for the thread. The setPriority() method can be invoked either before the thread is started or once it is running. Some VMs might allow you to change the priority of the system threads, but this is not recommended—even if it is permitted.

The class SetPriority, shown in Listing 6.3, demonstrates how setPriority() can be used to specify a priority of a thread before it is started. It also shows how the priority of a running thread can be changed on-the-fly.

Example 6.3. SetPriority.java—Changing the Priority of a Thread with setPriority()

 1: public class SetPriority extends Object {
 2:     private static Runnable makeRunnable() {
 3:         Runnable r =  new Runnable() {
 4:                 public void run() {
 5:                     for ( int i = 0; i < 5; i++ ) {
 6:                         Thread t = Thread.currentThread();
 7:                         System.out.println(
 8:                             "in run() - priority=" +
 9:                             t.getPriority() +
10:                             ", name=" + t.getName());
11:
12:                         try {
13:                             Thread.sleep(2000);
14:                         } catch ( InterruptedException x ) {
15:                             // ignore
16:                         }
17:                     }
18:                 }
19:             } ;
20:
21:         return r;
22:     }
23:
24:     public static void main(String[] args) {
25:         Thread threadA = new Thread(makeRunnable(), "threadA");
26:         threadA.setPriority(8);
27:         threadA.start();
28: 
29:         Thread threadB = new Thread(makeRunnable(), "threadB");
30:         threadB.setPriority(2);
31:         threadB.start();
32:
33:         Runnable r = new Runnable() {
34:                 public void run() {
35:                     Thread threadC =
36:                         new Thread(makeRunnable(), "threadC");
37:                     threadC.start();
38:                 }
39:             } ;
40:         Thread threadD = new Thread(r, "threadD");
41:         threadD.setPriority(7);
42:         threadD.start();
43:
44:         try { Thread.sleep(3000); } 
45:         catch ( InterruptedException x ) { }
46:
47:         threadA.setPriority(3);
48:         System.out.println("in main() - threadA.getPriority()=" +
49:                 threadA.getPriority());
50:     }
51: }

The makeRunnable() method of SetPriority works the same as it did in GetPriority. This time several new threads are spawned inside main(). First, threadA is created and has its priority set to 8 before it is started (lines 25–27). Next, threadB is created and has its priority set to 2 before it is started (lines 29–31).

To demonstrate how a thread priority is inherited, threadD creates threadC (lines 33–42). The initial priority for threadD is set to 7 before it is started (line 41). When threadD runs, it executes the run() method inside r (lines 34–38). A new Thread is constructed by threadD and is called threadC. The priority of threadC is not changed, but is simply inherited from threadC's creator.

After launching the threads, the main thread sleeps for three seconds. When it wakes up, it invokes setPriority() on the already running thread threadA to change its priority to 3.

Listing 6.4 shows the output produced when SetPriority is run. Your output should be very similar, but might differ slightly.

Example 6.4. Possible Output from SetPriority

 1: in run() - priority=8, name=threadA
 2: in run() - priority=2, name=threadB
 3: in run() - priority=7, name=threadC
 4: in run() - priority=8, name=threadA
 5: in run() - priority=2, name=threadB
 6: in run() - priority=7, name=threadC
 7: in main() - threadA.getPriority()=3
 8: in run() - priority=3, name=threadA
 9: in run() - priority=2, name=threadB
10: in run() - priority=7, name=threadC
11: in run() - priority=3, name=threadA
12: in run() - priority=2, name=threadB
13: in run() - priority=7, name=threadC
14: in run() - priority=3, name=threadA
15: in run() - priority=2, name=threadB
16: in run() - priority=7, name=threadC

While examining the output from SetPriority, you can see that threadC is running with a priority of 7 (lines 3, 6, 10, 13, and 16). It was never directly set to 7, but was defaulted to that level when it was created by threadD. Also note that when the priority of threadA was reduced to 3 from 8, threadA printed the new priority the last three times through its for loop (lines 8, 11, and 14).

When setPriority() Might Not Work

Calls to setPriority can throw a SecurityException if the calling thread is not permitted to make the priority change. This will happen only if there is a SecurityManager installed and it rejects the change request. By default, applications do not have a SecurityManager.

An IllegalArgumentException will be thrown if the specified priority is greater than Thread.MAX_PRIORITY or less than Thread.MIN_PRIORITY.

The value passed to setPriority() can be silently reduced to the maximum priority allowed for the thread group. For example, in this code fragment

Thread t = //...
ThreadGroup tg = t.getThreadGroup();
int groupMax = tg.getMaxPriority();

the maximum value for the thread priority of thread t is groupMax. Usually, this is the same as Thread.MAX_PRIORITY. When groupMax is less than Thread.MAX_PRIORITY, calls to setPriority() still work, but will silently reduce the value passed to groupMax. If groupMax is 6 and setPriority(9) is called, the code will run as if setPriority(6) was called instead. See Chapter 10, "Thread Groups," for more on ThreadGroup.

Caution

The Java 1.1 VM inside Netscape's Navigator/Communicator (versions 4.0 through 4.5) silently ignores all calls to setPriority(). Even calls that would lower the priority of a thread are ignored! No exception is thrown, but the thread priority remains unchanged. Netscape implements an elaborate security scheme that requires a bunch of code to tell the VM to allow thread priorities to be changed. Future versions of Netscape might change this policy, but beware of the potential problems that applets might have running in this version of the browser. If thread prioritization is important in your applet, consider requiring the Java plug-in that Sun Microsystems supplies for free.

Thread States

While a thread is alive, it is in one of several states. On a single-processor system, only one thread is actually running at a time. The other threads are either blocked or are ready-to-run and waiting for a turn on the processor. Figure 6.1 lists the various states that an active thread can be in.

Thread states.

Figure 6.1. Thread states.

The thread scheduler controls which one of the ready-to-run threads is actually running on the processor. Only one thread is actually in the running state at any given time. All the other threads that are ready-to-run wait to be picked by the thread scheduler. Thread priority helps the thread scheduler choose which of the ready-to-run threads should be run next.

Threads can be blocked in one of four states. When a thread executes Thread.sleep(), it blocks until the specified number of milliseconds passes or until it is interrupted by another thread. When a thread encounters a wait() statement (see Chapter 8, "Inter-thread Communication," for more on the wait/notify mechanism), it blocks until it is notified, interrupted, or the specified number of milliseconds elapses (if a timeout was specified).

There are many ways a thread can block waiting on different I/O methods. One common way is the read() method of InputStream. This method blocks until a byte of data is read from the stream. It can block indefinitely, and no timeout can be specified.

A thread can also block waiting to acquire exclusive access to an object's lock. The synchronized statement and synchronized method modifier (see Chapter 7, "Concurrent Access to Objects and Variables," for more on synchronization) are used to control concurrent access by more than one thread to a section of code. A thread will block on synchronized until it gets the specified lock. It can block indefinitely, and no timeout can be specified.

Notice that not all blocked states are interruptible. When a thread is blocked waiting to read a byte of data from an InputStream, it will not respond to interrupts (see Chapter 15, "Breaking Out of a Blocked I/O State," for some techniques for dealing with this). When a thread is blocked waiting to get the lock required by a synchronized statement, it also will not respond to interrupt requests (see Chapter 17, "BooleanLock Utility," for a technique that deals with long waits for locks).

When conditions change and a thread is no longer blocked, the thread moves to the ready-to-run state. It remains there until selected to run by the thread scheduler. Figure 6.2 shows how threads transition from one state to another.

Only one thread is actually running at a time, and it eventually blocks, yields, or is forcibly swapped off the processor by the thread scheduler. If it blocks, it remains blocked until some condition changes. When it finally unblocks, it moves to the ready-to-run state. When new threads are started, they are put into the ready-to-run state.

When the running state is open and there is at least one thread in the ready-to-run state, the thread scheduler chooses one from the pool of ready-to-run threads and transitions it to the running state. Threads of higher priority are more likely to be chosen than threads of lower priority, but the exact behavior is dependent on the VM and the operating system.

Thread state transition diagram.

Figure 6.2. Thread state transition diagram.

Tip

These six states are not officially defined in Java, but are a useful tool for understanding thread behavior. The thread state transition diagram in Figure 6.2 is only a model that is helpful in imagining the inner workings of the VM. Most VM implementations use the underlying operating system's threads and thread scheduler. This model helps conceptualize what might be going on inside the VM, but it does not map directly to a real implementation.

Priorities and Scheduling

The virtual machine's thread scheduler determines which thread is currently running on the processor and how long it is allowed to run before being swapped off the processor to allow another thread to run. Thread priorities provide additional information to the thread scheduler as to which threads are more important to run.

Generally, higher-priority threads get more processor time than lower-priority threads. The Java Language Specification does not require any more specific scheduling selection than this.

In particular, a low-priority thread may not run until all of the threads with a higher priority are blocked (sleeping, waiting on I/O, or some other blocked condition). It is likely (but not guaranteed) that a low-priority thread will be swapped off the processor as soon as a higher-priority thread unblocks and becomes ready-to-run. Threads of equal priority may or may not share the processor with each other. In addition, most VM implementations use the underlying operating system's threads. Exact thread behavior varies from VM to VM, and you need to be careful that your designs do not critically depend on one particular implementation.

Caution

Thread priorities are only a suggestion to the thread scheduler and should not be used to guarantee program correctness. Write your programs in such a way that they would still produce correct results regardless of prioritization.

Voluntarily Relinquishing the Processor: Thread.yield()

To help ensure that other threads in the VM get a turn to run on the processor, a thread can voluntarily give up its turn early. If a thread invokes the static method Thread.yield(), the thread scheduler will swap it off the processor and allow another thread to run. It is likely (but not guaranteed) that only threads having a priority equal to or greater than the one that yielded control will be considered by the thread scheduler.

A thread implicitly yields the processor when it goes to sleep or otherwise blocks. The Thread.yield() method allows a thread to specify other times that are convenient for it to pause to allow other threads access to the processor. If you have a thread that frequently blocks on its own, there is no need to make Thread.yield() calls. But, if you have a thread that is performing a long non-blocking calculation, an occasional call to Thread.yield() can help split up the processor resources among the other threads. Be careful not to overuse Thread.yield() as some system overhead is incurred to perform a context switch between threads. As a rough guideline, try to avoid calling Thread.yield() more than five times per second.

The PriorityCompete class shown in Listing 6.5 creates three threads that all compete with each other to run nonblocking code. It runs once without using Thread.yield() and then a second time with voluntary yielding.

Example 6.5. PriorityCompete.java—Competing Threads of Different Priorities

  1: public class PriorityCompete extends Object {
  2:     private volatile int count;
  3:     private boolean yield;
  4:     private Thread internalThread;
  5:     private volatile boolean noStopRequested;
  6:
  7:     public PriorityCompete(
  8:                 String name,
  9:                 int priority,
 10:                 boolean yield
 11:             ) {
 12:
 13:         count = 0;
 14:         this.yield = yield;
 15:
 16:         noStopRequested = true;
 17:         Runnable r = new Runnable() {
 18:                 public void run() {
 19:                     try {
 20:                         runWork();
 21:                     } catch ( Exception x ) {
 22:                         // in case ANY exception slips through
 23:                         x.printStackTrace();
 24:                     }
 25:                 }
 26:             } ;
 27:
 28:         internalThread = new Thread(r, name);
 29:         internalThread.setPriority(priority);
 30:     }
 31:
 32:     private void runWork() {
 33:         Thread.yield();
 34:
 35:         while ( noStopRequested ) {
 36:             if ( yield ) {
 37:                 Thread.yield();
 38:             }
 39: 
 40:             count++;
 41:
 42:             for ( int i = 0; i < 1000; i++ ) {
 43:                 double x = i * Math.PI / Math.E;
 44:             }
 45:         }
 46:     }
 47:
 48:     public void startRequest() {
 49:         internalThread.start();
 50:     }
 51:
 52:     public void stopRequest() {
 53:         noStopRequested = false;
 54:     }
 55:
 56:     public int getCount() {
 57:         return count;
 58:     }
 59:
 60:     public String getNameAndPriority() {
 61:         return internalThread.getName() +
 62:             ": priority=" + internalThread.getPriority();
 63:     }
 64:
 65:     private static void runSet(boolean yield) {
 66:         PriorityCompete[] pc = new PriorityCompete[3];
 67:         pc[0] = new PriorityCompete("PC0", 3, yield);
 68:         pc[1] = new PriorityCompete("PC1", 6, yield);
 69:         pc[2] = new PriorityCompete("PC2", 6, yield);
 70:
 71:         // let the dust settle for a bit before starting them up
 72:         try { Thread.sleep(1000); } 
 73:         catch ( InterruptedException x ) { }
 74:
 75:         for ( int i = 0; i < pc.length; i++ ) {
 76:             pc[i].startRequest();
 77:         }
 78: 
 79:         long startTime = System.currentTimeMillis();
 80:         try { Thread.sleep(10000); } 
 81:         catch ( InterruptedException x ) { }
 82:
 83:         for ( int i = 0; i < pc.length; i++ ) {
 84:             pc[i].stopRequest();
 85:         }
 86:
 87:         long stopTime = System.currentTimeMillis();
 88:
 89:         // let things settle down again
 90:         try { Thread.sleep(1000); } 
 91:         catch ( InterruptedException x ) { }
 92:
 93:         int totalCount = 0;
 94:         for ( int i = 0; i < pc.length; i++ ) {
 95:             totalCount += pc[i].getCount();
 96:         }
 97:
 98:         System.out.println("totalCount=" + totalCount +
 99:             ", count/ms=" + roundTo(((double) totalCount) /
100:                                     (stopTime - startTime), 3));
101:
102:         for ( int i = 0; i < pc.length; i++ ) {
103:             double perc = roundTo(100.0 * pc[i].getCount() /
104:                                     totalCount, 2);
105:             System.out.println(pc[i].getNameAndPriority() +
106:                 ", " + perc + "%, count=" + pc[i].getCount());
107:         }
108:     }
109:
110:     public static double roundTo(double val, int places) {
111:         double factor = Math.pow(10, places);
112:         return ( (int) ( ( val * factor ) + 0.5 ) ) / factor;
113:     }
114:
115:     public static void main(String[] args) {
116:         Runnable r = new Runnable() {
117:                 public void run() {
118:                     System.out.println(
119:                             "Run without using yield()");
120:                     System.out.println(
121:                             "=========================");
122:                     runSet(false);
123:
124:                     System.out.println();
125:                     System.out.println("Run using yield()");
126:                     System.out.println("=================");
127:                     runSet(true);
128:                 }
129:             } ;
130: 
131:         Thread t = new Thread(r, "PriorityCompete");
132:         t.setPriority(Thread.MAX_PRIORITY - 1);
133:         t.start();
134:     }
135: }

In the main() method of PriorityCompete (lines 115–134), a new thread is created to run at a higher priority than the main thread normally does. This thread is named PriorityCompete (line 131) and runs at almost the maximum priority allowed (line 132). When it is started (line 133), the run() method (lines 117–128) is executed by the PriorityCompete thread. First it calls runSet(), passing false to indicate that the threads should not voluntarily yield to one another (line 122). After that completes, it calls runSet() again, this time passing in true to indicate that the threads should yield (line 127).

Each time the runSet() method (lines 65–108) is called, it creates three instances of PriorityCompete (lines 66–69). Two of them will run at a priority level of 6 and the third will run at a lower priority level of 3. The PriorityCompete thread then sleeps for a second (line 72) to allow the VM to settle down (making sure that the main thread has had an opportunity to leave main(), among other things). It starts each of the objects running (lines 75–77). Next, it takes note of the time (line 79), and then lets the objects compete for the processor for 10 seconds (line 80). After running for 10 seconds, they are stopped (lines 83–85), and the time is noted again (line 87). The time is requested because the sleep is usually a little longer than 10 seconds because of all the other processing activity.

Each of the PriorityCompete objects increments its internal counter. The final count that each achieved is retrieved and totaled (lines 93–96). The total count and the count increments per millisecond are printed (lines 98–100). Finally, the count for each PriorityCompete object is printed along with the percent of the total that its count represents (lines 102–107). The roundTo() method (lines 110–113) is simply used to round a double to the specified number of decimal places.

Each instance of PriorityCompete creates its own internal thread that will run at the specified priority (lines 7–30). PriorityCompete is roughly based on the self-running object technique shown in Chapter 11, "Self-Running Objects." The internal thread is created (line 28) and has its priority set (line 29), but is not started until startRequest() (lines 48–50) is called. The stopRequest() method (lines 52–54) simply changes an indicator variable so that the next time the internal thread checks, it returns from run(). The getCount() method (lines 56–58) and the getNameAndPriority() method (lines 60–63) are used in runSet() to retrieve information.

Each of the internal threads ends up invoking its own runWork() method (lines 32–46). Right away, it yields to allow all the threads to get a chance to start running (line 33). The while loop (lines 35–45) is executed until a stop request is received. Each time though the loop, if this instance is supposed to yield, it executes Thread.yield() (lines 36–38). Then it increments the internal counter (line 40). To slow down the progress through the loop, some busy work is done (lines 42–44). This busy work does not block, but keeps the processor working.

Listing 6.6 shows possible output when PriorityCompete is run. Your output is likely to differ significantly, as it is heavily dependent on how powerful the hardware is and how many other processes are running on the operating system.

Example 6.6. Possible Output from PriorityCompete

 1: Run without using yield()
 2: =========================
 3: totalCount=764469, count/ms=76.523
 4: PC0: priority=3, 4.65%, count=35510
 5: PC1: priority=6, 48.19%, count=368395
 6: PC2: priority=6, 47.17%, count=360564
 7:
 8: Run using yield()
 9: =================
10: totalCount=253523, count/ms=25.352
11: PC0: priority=3, 0.01%, count=31
12: PC1: priority=6, 49.99%, count=126739
13: PC2: priority=6, 50.0%, count=126753

In the first set (lines 1–6), the internal threads never block and only reluctantly relinquish the processor when the thread scheduler forces them into the ready-to-run state. Even though there is always a thread with a priority of 6 ready-to-run, the thread scheduler still gives the thread with a priority of 3 a chance to run now and then—just less than 5% of the time (line 4). Otherwise, the remaining percentage is roughly split between the two priority 6 threads (lines 5–6).

In the second set (lines 8–13), the internal threads yield each time though the loop. In this case, the lower-priority thread gets much less time (but not zero time) on the processor and gets only 0.01% of the processor resources. The high-priority threads split the time almost perfectly in half between themselves: 49.99% and 50.00%.

Also note that without yielding, the count/ms was about 76 (line 3), and when yielding was done, it was cut down to about 25 (line 10). This shows that the yielding caused a lot of context switches that brought with them some significant overhead. In this example, the excessive yielding wasted processor resources.

Thread-Scheduling Scenarios

There are many thread-scheduling algorithms that might be used on a particular platform. If you simultaneously consider the three following scenarios, you should end up with a good design that will work well on a wide variety of VM implementations.

Scenario I: One High-Priority Thread Hogs Processor

The highest-priority thread that is ready-to-run gets to run until it blocks, it yields the processor, or an even higher-priority thread becomes ready-to-run. In this case, the thread scheduler does not force the thread to move off the processor, but it waits for some condition to change. In this scenario, the following events can happen:

  • Lower-priority threads may never get a chance to run and may become starved for processor time.

  • Threads of equal priority don't get a chance to run until the running thread yields or blocks.

  • If a thread of higher priority becomes ready-to-run, it will pre-empt the currently running thread.

Scenario II: All High-Priority Threads Hog Processor

This is a slight variation on Scenario I where the thread scheduler swaps threads of equal priority with each other. Now the running thread will continue to run until it blocks, it yields the processor, or a thread of higher or equal priority becomes ready-to-run:

  • Lower-priority threads may never get a chance to run and may become starved for processor time.

  • Threads of equal priority each get a chance to run.

  • If a thread of higher priority becomes ready-to-run, it will pre-empt the currently running thread.

Scenario III: All Threads Get Some Time on the Processor

In this case, the thread scheduler makes sure that all threads get some time on the processor regardless of their priority. However, higher-priority threads will be scheduled for more processor time than lower-priority threads. This is the behavior observed in Listing 6.6:

  • Lower-priority threads will get some processor time, although it will be less than higher-priority threads get.

  • Threads of equal priority each get a chance to run.

  • When higher-priority threads become ready-to-run, they will generally pre-empt a thread that is running at a lower priority. But, because all threads get some time on the processor, there might be short periods of time when a high-priority thread is ready-to-run, but is held back briefly while a lower-priority thread gets a small slice of time.

This last scenario is really the ideal case, but you will need to code with the other two in mind to be sure that each thread gets some time on the processor—regardless of the particular VM that the code is running on.

Tip

When assigning priorities to the threads in your application, use the higher priorities only for threads that block frequently (sleeping, waiting, I/O). CPU-intensive calculations should be done with a medium- to low-priority thread to ensure that the processor is not hogged. Avoid setting a priority to Thread.MAX_PRIORITY unless the thread spends nearly all of its time blocked or is very short-lived.

Summary

In this chapter I showed you how to use a few more parts of the Thread API:

  • Thread.MAX_PRIORITY, Thread.NORM_PRIORITY, and Thread.MIN_PRIORITY

  • getPriority()

  • setPriority()

  • Thread.yield()

The thread scheduler chooses which thread to move from the ready-to-run state to the running state. Thread priorities provide suggestions to the scheduler to help it pick the next thread to run. Generally, higher-priority threads get more processor time than lower-priority threads.

Thread scheduling varies from VM to VM and from operating system to operating system. Because of these differences, prioritization should only be used to try to improve responsiveness and efficiency, and should not be relied upon for program correctness.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.79.45