Scheduling with Thread Priorities

Let’s delve into the programming that affects thread scheduling; we’ll start by examining how to manipulate the priority level of Java threads. This is the most useful mechanism available to a Java programmer that affects scheduling behavior of threads; often, a few simple adjustments of thread priorities is all that’s required to make a program behave as desired.

Priority-Related Calls in the Java API

In the Java Thread class, there are three static final variables that define the allowable range of thread priorities:

Thread.MIN_PRIORITY

The minimum priority a thread can have

Thread.MAX_PRIORITY

The maximum priority a thread can have

Thread.NORM_PRIORITY

The default priority for threads in the Java interpreter

Every thread has a priority value that lies somewhere in the range between MIN_PRIORITY (which is 1) and MAX_PRIORITY (which is 10). However, not all threads can have a value anywhere within this range: each thread belongs to a thread group, and the thread group has a maximum priority (lower than or equal to MAX_PRIORITY) that its individual threads cannot exceed. We’ll discuss this further in Chapter 10, but for now, you should be aware that the maximum thread priority for a thread within an applet is typically NORM_PRIORITY + 1. In addition, the virtual machine is allowed to create internal threads at a priority of 0, so that there are in effect 11 different priority levels for threads within the virtual machine.

The default priority of a thread is the priority of the thread that created it. This is often, but not always, NORM_PRIORITY (which is 5).

There are two methods in the Java Thread class that relate to the priority of a thread:

void setPriority(int priority)

Sets the priority of the given thread. If priority is outside the allowed range of thread priorities, an exception is thrown. However, if the priority is within the allowed range of thread priorities but is greater than the maximum priority of the thread’s thread group, then the priority is silently lowered to match that maximum priority.

int getPriority()

Retrieves the priority of the given thread.

Using the Priority Calls

Let’s look at an example of using these calls. Often, simply setting the priority of each of your threads is sufficient to achieve the required scheduling. If you have two threads in your program and one is usually blocked, all you need to do is set the priority of the thread that blocks above the priority of the other thread, and you’ll prevent CPU starvation. We’ll illustrate this example with a code fragment that is designed to calculate and display fractal images. The calculation of the fractal is very CPU intensive but has the advantage that it can be done in sections that can be displayed as each is computed. So we’ll put the actual calculations into a separate, low-priority thread that calls the repaint() method after each section has been calculated. Meanwhile, our applet’s initial thread spends most of its time blocked, waiting for an event from the user or for a repaint event.

Here’s the skeleton code for our fractal applet:

import java.applet.*;
import java.awt.*;

public class Fractal extends Applet implements Runnable {
    Thread calcThread;
    boolean sectionsToCalculate;
    static int nSections = 10;

    public void start() {
        Thread current = Thread.currentThread();
        calcThread = new Thread(this);
        calcThread.setPriority(current.getPriority() - 1);
        calcThread.start();
    }

    public void stop() {
        sectionsToCalculate = false;
    }

    void doCalc(int i) {
        // Calculate section i of the fractal.
    }

    public void run() {
        for (int i = 0; i < nSections && sectionsToCalculate; i++) {
            doCalc(i);
            repaint();
        }
    }

    public void paint(Graphics g) {
        // Paint the calculated sections.
    }           
}

Consider what would happen in this example if we didn’t lower the priority of the calculation thread. In that case, the applet would run through its init() and start() methods, and we’d be left with two threads at NORM_PRIORITY: the applet thread and the calculation thread. The applet thread blocks waiting for an event from the windowing system, so the calculation thread is the only runnable thread and hence becomes the currently running thread. The calculation thread calculates a section of the fractal and calls the repaint() method. This creates the necessary event to unblock the applet thread and move the applet thread into the runnable state.

However, the calculation thread is still in the runnable state, which means that the calculation thread remains the currently running thread. The applet thread is added to the end of the NORM_PRIORITY list, and if our Java virtual machine does not perform round-robin scheduling, the calculation thread will always remain the currently running thread. Thus, as long as there are sections of the fractal to calculate, the many calls to the repaint() method have no effect: the applet thread never gets the opportunity to become the currently running thread and repaint the screen.

If, however, we set the priority of the calculation thread lower than the priority of the applet thread, then when the calculation thread calls the repaint() method, the applet thread becomes the currently running thread since it is now the runnable thread with the highest priority. The applet thread executes the paint() method and moves again to the blocked state, allowing the calculation thread to become the currently running thread.

Note that this technique is also important if the user might interact with the applet while the fractal is calculating. If the calculation thread is at the same priority as the default applet thread, then the applet will not be able to respond to user input while the calculation thread is running.

When to Use Simple Priority-Based Calls

What are the circumstances in which this technique of setting the priority of certain threads is appropriate? You’ll use this technique when both of the following are true:

  • There is only one CPU-intensive thread (or one thread per CPU on the target machine).

  • Intermediate results are interesting to the user.

That’s clearly the case of the fractal calculation: there’s one thread calculating the sections of the fractal, and each section is an interesting intermediate result. Mathematical models often benefit from the notion of successive refinement.

Image loading is another area where intermediate results are often important to the user: as parts of the image become available, they can be drawn on the screen so that the user sees them “scrolled” onto the screen. But remember: in the typical case, the Java program is loading the image over the network, which means that the thread reading the image will often block, so that there is no need to adjust any thread’s priority. But if the Java program is calculating the image from some preloaded data set, lowering the priority of that thread is a good idea.

What if we had more than one CPU-intensive thread? In the case of the fractal, what if we’d set up a separate thread to calculate each section of the fractal? This is a programmatically elegant solution, but there’s a danger here. When you have more than one CPU-intensive thread, you should lower the priority of each of the CPU-intensive threads. In that case, as long as each calculation thread is at a lower level than the applet thread, you get at least part of the behavior you want.

This may or may not give you the entire behavior that you want. On platforms that support round-robin scheduling among threads of equal priority, CPU-intensive threads will compete for the CPU, and the individual calculation of each section will take longer than if the calculation of an individual section is allowed to run to completion. This means that the user sees the sections of the fractal (that is, the intermediate feedback) more slowly than in the case where there is a single calculation thread.

On the other hand, if your program has as many CPU-intensive threads as the machine that it’s running on has processors, then by using this technique you’ll get the most out of the machine’s resources and see the intermediate results as quickly as possible.

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

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