Friday, February 25, 2011

Chapter 59: Thread Priorities, yield( ) and Join

The last chapter in the series of chapters on Threads is going to be about thread priorities and using the yield() & join() methods. I know you are eager to finish off threads on the SCJP syllabus, so am I.

So, lets get started!!!

Thread Priorities

Threads always run with some priority, usually represented as a number between 1 and 10 (although in some cases the range is less than 10). The scheduler in most JVMs uses preemptive, priority-based scheduling (which implies some sort of time slicing). This does not mean that all JVMs use time slicing. The JVM specification does not require a VM to implement a time-slicing scheduler, where each thread is allocated a fair amount of time and then sent back to runnable to give another thread a chance. Although many JVMs do use time slicing, some may use a scheduler that lets one thread stay running until the thread completes its run() method.

In most JVMs, however, the scheduler does use thread priorities in one important way: If a thread enters the runnable state, and it has a higher priority than any of the threads in the pool and a higher priority than the currently running thread, the lower-priority running thread usually will be bumped back to runnable and the highest-priority thread will be chosen to run. In other words, at any given time the currently running thread usually will not have a priority that is lower than any of the threads in the pool. In most cases, the running thread will be of equal or greater priority than the highest priority threads in the pool. This is as close to a guarantee about scheduling as you’ll get from the JVM specification, which means, you must or rather you can never rely on thread priorities to guarantee the correct behavior of your program.

What is also not guaranteed is the behavior when threads in the pool are of equal priority, or when the currently running thread has the same priority as threads in the pool. All priorities being equal, a JVM implementation of the scheduler is free to do just about anything it likes. That means a scheduler is gonna be just like your boss as explained in the previous chapter. The scheduler might do one of the following:

• Pick a thread to run, and run it there until it blocks or completes.
• Time slice the threads in the pool to give everyone an equal opportunity to run.

Setting a Thread’s Priority

A thread gets a default priority that is the priority of the thread of execution that creates it. For example, in the code
public class TestMyNewThreads {
public static void main (String [] args) {
MyNewThread t = new MyNewThread();

The thread referenced by t will have the same priority as the main thread, since the main thread is executing the code that creates the MyNewThread instance.

You can also set a thread’s priority directly by calling the setPriority() method on a Thread instance as follows:
MyNewThread1 r = new MyNewThread1();
Thread t = new Thread(r);

Priorities are set using a positive integer, usually between 1 and 10, and the JVM will never change a thread’s priority. However, the values 1 through 10 are not guaranteed. Some JVM’s might not recognize ten distinct values. Such a JVM might merge values from 1 to 10 down to maybe values from 1 to 5, so if you have, say, ten threads each with a different priority, and the current application is running in a JVM that allocates a range of only five priorities, then two or more threads might be mapped to one priority.

Although the default priority is 5, the Thread class has the three following constants (static final variables) that define the range of thread priorities:

Thread.MAX_PRIORITY (10)

The yield( ) Method

So what does the static Thread.yield() have to do with all this? Not that much, actually. What yield() is supposed to do is make the currently running thread go back to runnable to allow other threads of the same priority to get their turn. So the intention to use yield() is to allow graceful turn-taking among equal-priority threads. In reality, though, the yield() method isn’t guaranteed to do what it claims, and even if yield() does cause a thread to step out of running and back to runnable, there’s no guarantee the yielding thread won’t just be chosen again over all the others! So while yield() might and often does make a running thread give up its slot to another runnable thread of the same priority, but there’s no guarantee.
A yield() won’t ever cause a thread to go to the waiting/sleeping/ blocking state. At most, a yield() will cause a thread to go from running to runnable, but again, it might have no effect at all.

The join( ) Method

The non-static join() method of class Thread lets one thread “join onto the end” of another thread. If you have a thread B that can’t do its work until another thread A has completed its work, then you want thread B to “join” thread A. This means that thread B will not become runnable until A has finished (and entered the dead state).

Thread t = new Thread();

The preceding code takes the currently running thread (if this were in the main() method, then that would be the main thread) and joins it to the end of the thread referenced by t. This blocks the current thread from becoming runnable until after the thread referenced by t is no longer alive. In other words, the code t.join() means “Join me (the current thread) to the end of t, so that t must finish before I (the current thread) can run again.” You can also call one of the overloaded versions of join() that takes a timeout duration, so that you’re saying, “wait until thread t is done, but if it takes longer than 5,000 milliseconds, then stop waiting and become runnable anyway.”

So far we’ve looked at three ways a running thread could leave the running state:
• A call to sleep() Guaranteed to cause the current thread to stop executing for at least the specified sleep duration (although it might be interrupted before its specified time).
• A call to yield() Not guaranteed to do much of anything, although typically it will cause the currently running thread to move back to runnable so that a thread of the same priority can have a chance.
• A call to join() Guaranteed to cause the current thread to stop executing until the thread it joins with (in other words, the thread it calls join() on) completes, or if the thread it’s trying to join with is not alive, however, the current thread won’t need to back out.
Besides those three, we also have the following scenarios in which a thread might leave the running state:
• The thread’s run() method completes.
• A call to wait() on an object.
• A thread can’t acquire the lock on the object whose method code it’s attempting to run.
• The thread scheduler can decide to move the current thread from running to runnable in order to give another thread a chance to run. No reason is needed—the thread scheduler can trade threads in and out whenever it likes.

Previous Chapter: Chapter 58 - Preventing Thread Execution

Next Chapter: Chapter 60 - Synchronization

No comments:

Post a Comment

© 2013 by All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.