16.4 Scheduling

On most computers, the CPU can execute only one thread at a time. Even on large multiprocessor computers, there are usually more threads than there are CPUs. Therefore, some threads must be suspended while other threads execute. In order to be fair, the system lets each thread run for some amount of time, then interrupts it and lets some other thread run. The plan for deciding which thread to execute and how long to let it run is called scheduling.

The JVM makes few promises about how scheduling occurs. Fairness is not guaranteed. This allows virtual machine implementers flexibility in porting the JVM to other operating systems. Some implementations have a separate native operating system level thread for each Java thread, and the scheduling behavior is consistent with the operating system's thread scheduler. This technique is usually used on Windows implementations and on Solaris 2.6.

Another common implementation uses "green threads," a threading library that doesn't depend on the native OS threads. Green threads are found in some older versions of Java, particularly on Unix-based systems. Green threads depend on the programmer to do most of the scheduling.

Ordinarily, a thread yields control of the CPU only when doing an activity involving operating system control, such as I/O or sleeping. If your application depends on fair division of CPU time among threads, it may help to include calls to Thread.yield, which permits the thread scheduler to give control to a different thread.

A portable JVM program cannot make any assumptions about thread scheduling. It may be interrupted at any point in its processing. It may also permit one thread to go forever without sharing the CPU.

You can make hints to the thread scheduler about priority with the Thread.setPriority method. This method takes an int between MIN_PRIORITY and MAX_PRIORITY. The JVM specification makes no guarantees about the meaning of priority levels, but in general higher values are more likely to get control of locks and are more likely to be scheduled for the CPU.

For example, to create a low-priority thread that uses spare CPU cycles to compute pi:

 Thread pi = new PiThread(); pi.setPriority(Thread.MIN_PRIORITY); pi.start(); 

You should not assume that a high-priority thread will always take precedence over a low-priority thread. The priority is only a hint to the scheduler, not a requirement. A high-priority thread may be put to sleep for a while to give a low-priority thread a chance to run.

Because low-priority threads may run even if higher-priority threads exist, it is not possible to write mutual exclusion code using thread priorities. The reasoning goes like this: if I have exactly one thread that has the highest priority, and I ask it to do all the work that may not be interrupted, then that work will always be done before any other thread is scheduled.

Unfortunately, this reasoning is incorrect. No matter what the priority distributions are, it is still possible for a low-priority thread to run instead of a high-priority thread. Instead, you should use the synchronization primitives through synchronized methods and monitor locks; the virtual machine guarantees that these will be effective.

16.4.1 Surrendering Control of the CPU

A thread may be interrupted at any time by the thread scheduler to allow another thread to run. In addition, a thread may voluntarily give up control of the CPU. It may do this while it's waiting for input from some device or just to be cooperative with the other threads.

One way to surrender control is to sleep, which causes the thread to stop for a certain number of milliseconds (or milliseconds and nanoseconds, if two arguments are provided, though few JVM implementations can actually deliver control at that fine-grained a level). When the specified period has passed, the thread again competes with the other threads for control of the CPU. It does not necessarily regain control immediately after sleeping, especially if there are other CPU-hungry threads running.

For example, a mail client may want to have a thread that polls the mail server for email every five minutes.

 .class PollThread .implements Runnable .method public run()V loop:    invokestatic PollThread/check_mail()V    ldc2_w 300000               ; Sleep for 5 minutes =                                ; 300,000 milliseconds                                ; before checking again begin:    invokestatic java/lang/Thread/sleep(J)V end:    goto loop .end method 

The yield method is similar to sleep, except no argument is required. Instead, the current thread goes back into the pool of threads waiting to execute. If there are no other threads running, the current thread continues immediately. If other threads are running, the JVM schedules the thread to run again. If the program is running on a system that doesn't divide time well between threads, you might throw in occasional yield calls:

 ;; Do a bunch of stuff ; Give other threads a chance to run invokestatic java/lang/Thread/yield()V ;; Do some more stuff 

This call helps the threads to share better.

16.4.2 Waiting for Another Thread

Another method similar to sleep is join. When you join to a thread, you wait until that thread finishes. For example, suppose you're writing a chess-playing program and you want to spawn off a number of threads to help make a decision. This will help take advantage of multiple CPUs. When all of the threads have completed, you make your move:

 // Consider all possible boards one move away from this position Board boards[] = current_board.nextMoves(); // Spawn off a thread for each board Thread threads = new Thread[boards.length]; for(int i = 0; i < boards.length; i++) {    // Create a thread to consider this board    threads[i] = new ChessThread(board[i]);    thread[i].start(); } // Now, wait for all threads to complete try {    for(int i = 0; i < threads.length; i++)       threads[i].join(); } catch(InterruptedException e) {    // We've been interrupted } 

This code waits for the first thread to terminate, then waits for the second thread, and so on. Some threads may wait a long time to return. Like sleep, join may take an argument that specifies how long to sleep. The program can set a time limit on the total time it will wait:

 int limit = 300000;              // Wait up to 5 minutes try {    for(int i = 0; i < threads.length && limit > 0; i++)    {       long begin = System.currentTimeMillis();       threads[i].join(limit);       long end = System.currentTimeMillis();       limit -= end - begin;            // Subtract time waited    } } catch(InterruptedException e) {    // We've been interrupted } 

The variable limit is set to the maximum waiting time. Each time it attempts to join with a thread (that is, wait for that thread to terminate), it calculates the beginning time and ending time of the waiting period. It subtracts that difference from the limit so that the next time the allowed wait time will be shorter. Eventually, either all the threads join, or the total time expires.

Programming for the Java Virtual Machine
Programming for the Javaв„ў Virtual Machine
ISBN: 0201309726
EAN: 2147483647
Year: 1998
Pages: 158
Authors: Joshua Engel

Similar book on Amazon

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