Race Conditions


Thread Priority and Scheduling

After all that has been said about multiple threads being able to run simultaneously, you should know that thread concurrency is not literally possible unless the machine running your program has multiple processors. If that is not the case, then concurrency is merely imitated by the JVM through a process called time scheduling where threads take turns using the CPU. The JVM determines which thread to run at any time through a priority-based scheduling algorithm. Threads have a priority that ranges in value from Thread.MIN_PRIORITY to Thread.MAX_PRIORITY. When a Thread is created, it is assigned the same priority as the thread that created it but it can be changed at any time later. A thread’s actual priority is capped by its ThreadGroup’s maximum priority which may be set through ThreadGroup.setMaxPriority(). Thread’s and ThreadGroup’s priority-related methods are listed in tables 16-7 and 16-8.

Table 16-7: Thread’s Priority-Related Methods

Method Name and Purpose

public final int getPriority()

Gets this thread’s priority.

public final void setPriority(int newPriority)

Sets this thread’s priority to the specified value or to its thread group’s maximum priority – whichever is smaller.

Table 16-8: ThreadGroup’s Priority-Related Methods

Method Name and Purpose

public final int getMaxPriority()

Gets this thread group’s maximum priority.

public final void setMaxPriority(int newMaxPriority)

Sets this thread group’s priority to the specified value or (if it has a parent thread group) to its parent’s maximum priority – whichever is smaller.

In order to accommodate many different platforms and operating systems, the Java programming language makes few guarantees regarding the scheduling of threads. The JVM chooses and schedules threads loosely according to the following algorithm:

  1. If one thread has a higher priority than all other threads, that is the one it will run.

  2. If more than one thread has the highest priority, one of them will be chosen to run.

  3. A thread that is running will continue to run until either:

    1. it voluntarily releases the CPU through yield() or sleep()

    2. it is blocked waiting for a resource

    3. it finishes its run() method

    4. a thread with a higher priority becomes runnable

A thread is said to be blocked if its execution has paused to wait for a resource to become available. For instance, a thread that is writing to a file might block several times while the system performs some low-level disk management routines. A thread is said to have been preempted if the JVM stops running it in order to run a higher priority thread. As a consequence of the JVM’s scheduling algorithm, if the currently running thread has equal or higher priority than its competitors and never blocks or voluntarily releases the CPU, it may prevent its competitors from running until it finishes. Such a thread is known as a “selfish” thread and the other threads are said to have “starved”. Thread starvation is generally not desirable. If a selfish thread has a higher priority than the AWT/Swing threads, for instance, then the user interface might actually appear to freeze. Therefore, it is incumbent upon the programmer to do his part to ensure fairness in scheduling. This is easily accomplished by inserting periodic calls to yield() (or sleep() when appropriate) within the run methods of long-running threads.

The next example takes two PiPanel1s and puts them in a window with two comboBoxes controlling the individual threads’ priorities. Start them both with their Play/Pause buttons and watch them go. Adjust their priorities to view the effect on their own and each other’s speeds.

Example 16.10: chap16.priority.PriorityPiPanel.java

image from book
 1     package chap16.priority; 2 3     import java.awt.BorderLayout; 4     import java.awt.GridLayout; 5     import java.awt.event.ActionEvent; 6     import java.awt.event.ActionListener; 7     import java.util.Vector; 8 9     import javax.swing.JComboBox; 10    import javax.swing.JFrame; 11 12    import chap16.pi.PiPanel1; 13 14    public class PriorityPiPanel extends PiPanel1 { 15      public PriorityPiPanel() { 16        Vector choices = new Vector(); 17        for (int i = Thread.MIN_PRIORITY; i <= Thread.MAX_PRIORITY; ++i) { 18          choices.add(new Integer(i)); 19        } 20        final JComboBox cb = new JComboBox(choices); 21        cb.setMaximumRowCount(choices.size()); 22        cb.setSelectedItem(new Integer(producerThread.getPriority())); 23        cb.addActionListener(new ActionListener() { 24          public void actionPerformed(ActionEvent e) { 25            Integer item = (Integer)cb.getSelectedItem(); 26            int priority = item.intValue(); 27            producerThread.setPriority(priority); 28          } 29        }); 30        buttonPanel.add(cb, BorderLayout.WEST); 31      } 32      public static void main(String[] arg) { 33        JFrame f = new JFrame(); 34        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 35        f.getContentPane().setLayout(new GridLayout(2, 1)); 36        f.getContentPane().add(new PriorityPiPanel()); 37        f.getContentPane().add(new PriorityPiPanel()); 38        f.pack(); 39        f.show(); 40      } 41     }
image from book

Use the following commands to compile and execute the example. From the directory containing the src folder:

  javac –d classes -sourcepath src src/chap16/priority/PriorityPiPanel.java  java –cp classes chap16.priority.PriorityPiPanel

Your Results May Vary

There is variation in how systems handle multiple threads and thread priorities. Some systems employ a thread management scheme known as time slicing that prevents selfish threads from taking complete control over the CPU by forcing them to share CPU time. Some systems map the range of Thread priorities into a smaller range causing threads with close priorities to be treated equally. Some systems employ aging schemes or other variations of the basic algorithm to ensure, for instance, that even low-priority threads have a chance. Do not count on the underlying system’s behavior for your program to run correctly. Program defensively (and politely) by using Thread.sleep(), Thread.yield() or your own mechanism for ensuring that your application threads share the CPU as intended. Do not write selfish threads unless there is a compelling need to do so.

Example 16.11 is intended to determine whether or not your system employs timeslicing. It creates two selfish maximum priority threads. Each of them maintains control over a variable that the other can see. Each of them monitors the other’s variable for changes in value. While the current thread is running, if the variable controlled by the other thread changes value, then the current thread can conclude that it was preempted by the other thread. Being a selfish, highest priority thread, the only possible explanation for its preemption is that the underlying system was responsible. The program ends when and if one of the threads notices that it has been preempted. The longer the program runs without either thread being preempted, the greater the chance that the system does not employ time-slicing. If either of the threads completes its loop without being preempted, the program will conclude that the system doesn’t employ time-slicing. It takes less than ten seconds on my computer for a thread to be preempted and the program to end. Figure 16-3 shows the console output from running example 16.11 on my computer.

Example 16.11: chap16.timeslicing.SliceMeter.java

image from book
 1    package chap16.timeslicing; 2 3    public class SliceMeter extends Thread { 4      private static int[] vals = new int[2]; 5      private static boolean usesTimeSlicing = false; 6 7      private int myIndex; 8      private int otherIndex; 9 10     private SliceMeter(int myIndex, int otherIndex) { 11       this.myIndex = myIndex; 12       this.otherIndex = otherIndex; 13       setPriority(Thread.MAX_PRIORITY); 14     } 15     public void run() { 16       int lastOtherVal = vals[otherIndex]; 17       for (int i = 1; i <= Integer.MAX_VALUE; ++i) { 18         if (usesTimeSlicing) { 19           return; 20         } 21         int curOtherVal = vals[otherIndex]; 22         if (curOtherVal != lastOtherVal) { 23           usesTimeSlicing = true; 24           int numLoops = curOtherVal - lastOtherVal; 25           lastOtherVal = curOtherVal; 26           System.out.println( 27             ("While meter" + myIndex + " waited, ") 28               + ("meter" + otherIndex + " looped " + numLoops + " times")); 29         } 30         vals[myIndex] = i; 31       } 32     } 33     public static void main(String[] arg) { 34       SliceMeter meter0 = new SliceMeter(0, 1); 35       SliceMeter meter1 = new SliceMeter(1, 0); 36       meter0.start(); 37       meter1.start(); 38       try { 39         meter0.join(); 40         meter1.join(); 41       } catch (InterruptedException e) { 42         e.printStackTrace(); 43       } 44       System.out.println("usesTimeSlicing = " + usesTimeSlicing); 45     } 46   }
image from book

Use the following commands to compile and execute the example. From the directory containing the src folder:

   javac –d classes -sourcepath src src/chap16/timeslicing/SliceMeter.java   java –cp classes chap16.timeslicing.SliceMeter

image from book

 While meter0 waited, meter1 looped 2648918 times usesTimeSlicing = true

image from book

Figure 16-3: Results of Running Example 16.11

Incidentally, this program illustrates the use of the join() method. Without the calls to join() in lines 39 and 40, the main thread would report that usesTimeSlicing was false and terminate before meter0 and meter1 had completed their work. By calling join(), SliceMeter instructs the current thread, “main”, to wait for meter0 and meter1 to terminate before continuing its own execution. The syntax for join() can be confusing. The statement “meter0.join()”, for instance, doesn’t tell meter0 to do anything. It tells the current thread to wait until meter0 has terminated. Thread’s join() methods are listed in table 16-9.

Table 16-9: Thread’s join() Methods

Method Name and Purpose

public final void join() throws InterruptedException

Causes the current thread to wait until this thread terminates.

public final void join(long millis) throws InterruptedException

Causes the current thread to wait no longer than the specified time until this thread terminates.

public final void join(long millis, int nanos) throws InterruptedException

Causes the current thread to wait no longer than the specified time until this thread terminates.

Quick Review

The JVM employs a preemptive algorithm for scheduling threads waiting to be run. Setting a thread’s priority can affect how and when the JVM schedules it, which, in the face of other threads competing for CPU time, can affect the thread’s performance. A thread should call yield() or sleep() or otherwise make provisions for sharing the CPU with competing threads. A thread that does not make provisions for sharing the CPU with competing threads (by calling yield(), sleep() or some other mechanism) is called a selfish thread. There is no guarantee that the underlying system will prevent selfish threads from taking complete control of the CPU. Because of variations in how systems handle the scheduling of threads, the correctness of a program’s behavior should not rely on the particulars of any system.




Java For Artists(c) The Art, Philosophy, and Science of Object-Oriented Programming
Java For Artists: The Art, Philosophy, And Science Of Object-Oriented Programming
ISBN: 1932504052
EAN: 2147483647
Year: 2007
Pages: 452

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net