Chapter 9: Threads


Certification Objective —Defining, Instantiating, and Starting Threads (Objective 4.1)

4.1 Write code to define, instantiate, and start new threads using both java.lang.Thread and java.lang. Runnable.

Imagine a stockbroker application with a lot of complex capabilities. One of its functions is "download last stock option prices," another is "check prices for warnings," and a third time-consuming operation is "analyze historical data for company XYZ."

In a single-threaded runtime environment, these actions execute one after another. The next action can happen only when the previous one is finished. If a historical analysis takes half an hour, and the user selects to perform a download and check afterward, the warning may come too late to, say, buy or sell stock as a result.

We just imagined the sort of application that cries out for multithreading. Ideally, the download should happen in the background (that is, in another thread). That way, other processes could happen, at the same time so that, for example, a warning could be communicated instantly. All the while, the user is interacting with other parts of the application. The analysis, too, could happen in a separate thread, so the user can work in the rest of the application while the results are being calculated.

So what exactly is a thread? In Java, "thread" means two different things:

  • An instance of class java.lang.Thread

  • A thread of execution

An instance of Thread is justan object. Like any other object in Java, it has variables and methods, and lives and dies on the heap. But a thread of execution is an individual process (a "lightweight" process) that has its own call stack. In Java, there is one thread per call stack—or, to think of it in reverse, one call stack per thread. Even if you don't create any new threads in your program, threads are back there running.

The main() method, that starts the whole ball rolling, runs in one thread, called (surprisingly) the main thread. If you looked at the main call stack (and you can, any time you get a stack trace from something that happens after main begins, but not within another thread), you'd see that main() is the first method on the stack— the method at the bottom. But as soon as you create a new thread, a new stack materializes and methods called from that thread run in a call stack that's separate from the main() call stack. That second new call stack is said to run concurrently with the main thread, but we'll refine that notion as we go through this chapter.

You might find it confusing that we're talking about code running concurrently—as if in parallel—given that there's only one CPU on most of the machines running Java. What gives? The JVM, which gets its turn at the CPU by whatever scheduling mechanism the underlying OS uses, operates like a mini-OS and schedules its own threads regardless of the underlying operating system. In some JVMs, the Java threads are actually mapped to native OS threads, but we won't discuss that here; native threads are not on the exam. Nor is it required to understand how threads behave in different JVM environments. In fact, the most important concept to understand from this entire chapter is this:

When it comes to threads, very little is guaranteed.

So be very cautious about interpreting the behavior you see on one machine as "the way threads work." The exam expects you to know what is and is not guaranteed behavior, so that you can design your program in such a way that it will work regardless of the underlying JVM. That's part of the whole point of Java.

On the Job 

Don't make the mistake of designing your program to be dependent on a particular implementation of the JVM. As you'll learn a little later, different JVMs can run threads in profoundly different ways. For example, one JVM might be sure that all threads get their turn, with a fairly even amount of time allocated for each thread in a nice, happy, round-robin fashion. But in other JVMs, a thread might start running and then just hog the whole show, never stepping out so others can have a turn. If you test your application on the "nice turn-taking" JVM, and you don't know what is and is not guaranteed in Java, then you might be in for a big shock when you run it under a JVM with a different thread scheduling mechanism.

The thread questions are among the most difficult questions on the exam. In fact, for most people they are the toughest questions on the exam, and with four objectives for threads you'll be answering a lot of thread questions. If you're not already familiar with threads, you'll probably need to spend some time experimenting. Also, one final disclaimer: This chapter makes almost no attempt to teach you how to design a good, safe, multithreaded application. We only scratch the surface of that huge topic in this chapter! You're here to learn the basics of threading, and what you need to get through the thread questions on the exam. Before you can write decent multithreaded code, however, you really need to study more on the complexities and subtleties of multithreaded code.

(Note: The topic of daemon threads is NOT on the exam. All of the threads discussed in this chapter are "user" threads. You and the operating system can create a second kind of thread called a daemon thread. The difference between these two types of threads (user and daemon) is that the JVM exits an application only when all user threads are complete—the JVM doesn't care about letting daemon threads complete, so once all user threads are complete, the JVM will shut down, regardless of the state of any daemon threads. Once again, this topic is NOT on the exam.)

Making a Thread

A thread in Java begins as an instance of java.lang.Thread. You'll find methods in the Thread class for managing threads including creating, starting, and pausing them. For the exam, you'll need to know, at a minimum, the following methods:

 start() yield() sleep() run() 

The action happens in the run() method. Think of the code you want to execute in a separate thread as the job to do. In other words, you have some work that needs to be done, say, downloading stock prices in the background while other things are happening in the program, so what you really want is that job to be executed in its own thread. So if the work you want done is the job, the one doing the work (actually executing the job code) is the thread. And the job always starts from a run() method as follows:

 public void run() {   // your job code goes here } 

You always write the code that needs to be run in a separate thread in a run() method. The run() method will call other methods, of course, but the thread of execution—the new call stack—always begins by invoking run(). So where does the run() method go? In one of the two classes you can use to define your thread job.

You can define and instantiate a thread in one of two ways:

  • Extend the java.lang.Thread class.

  • Implement the Runnable interface.

You need to know about both for the exam, although in the real world you're much more likely to implement Runnable than extend Thread. Extending the Thread class is the easiest, but it's usually not a good OO practice. Why? Because subclassing should be reserved for specialized versions of more general superclasses. So the only time it really makes sense (from an OO perspective) to extend Thread is when you have a more specialized version of a Thread class. In other words, because you have more specialized thread-specific behavior. Chances are, though, that the thread work you want is really just a job to be done by a thread. In that case, you should design a class that implements the Runnable interface, which also leaves your class free to extend from some other class.

Defining a Thread

To define a thread, you need a place to put your run() method, and as we just discussed, you can do that by extending the Thread class or by implementing the Runnable interface. We'll look at both in this section.

Extending java.lang.Thread

The simplest way to define code to run in a separate thread is to

  • Extend the java.lang.Thread class.

  • Override the run() method.

It looks like this:

 class MyThread extends Thread {    public void run() {      System.out.println("Important job running in MyThread");    } } 

The limitation with this approach (besides being a poor design choice in most cases) is that if you extend Thread, you can't extend anything else. And it's not as if you really need that inherited Thread class behavior, because in order to use a thread you'll need to instantiate one anyway.

Keep in mind that you're free to overload the run() method in your Thread subclass:

 class MyThread extends Thread {   public void run() {     System.out.println("Important job running in MyThread");   }   public void run(String s) {     System.out.println("String in run is " + s);   } } 

But know this: The overloaded run(String s) method will be ignored by the Thread class unless you call it yourself. The Thread class expects a run() method with no arguments, and it will execute this method for you in a separate call stack after the thread has been started. With a run(String s) method, the Thread class won't call the method for you, and even if you call the method directly yourself, execution won't happen in a new thread of execution with a separate call stack. It will just happen in the same call stack as the code that you made the call from, just like any other normal method call.

Implementing java.lang.Runnable

Implementing the Runnable interface gives you a way to extend from any class you like, but still define behavior that will be run by a separate thread. It looks like this:

 class MyRunnable implements Runnable {    public void run() {      System.out.println("Important job running in MyRunnable");    } } 

Regardless of which mechanism you choose, you've now got yourself some code that can be run by a thread of execution. So now let's take a look at instantiating your thread-capable class, and then we'll figure out how to actually get the thing running.

Instantiating a Thread

Remember, every thread of execution begins as an instance of class Thread. Regardless of whether your run() method is in a Thread subclass or a Runnable implementation class, you still need a Thread object to do the work.

If you extended the Thread class, instantiation is dead simple (we'll look at some additional overloaded constructors in a moment):

 MyThread t = new MyThread( ) 

If you implement Runnable, instantiation is only slightly less simple. To have code run by a separate thread, you still need a Thread instance. But rather than combining both the thread and the job (the code in the run() method) into one class, you've split it into two classes—the Thread class for the thread-specific code and your Runnable implementation class for your job-that-should-be-run-by-a-thread code. (Another common way to think about this is that the Thread is the "worker," and the Runnable is the "job" to be done.)

First, you instantiate your Runnable class:

 MyRunnable r = new MyRunnable(); 

Next, you get yourself an instance of java.lang.Thread (somebody has to run your job), and you give it your job!

 Thread t = new Thread(r);   // Pass your Runnable to the Thread 

If you create a thread using the no-arg constructor, the thread will call its own run() method when it's time to start working. That's exactly what you want when you extend Thread, but when you use Runnable, you need to tell the new thread to use your run() method rather than its own. The Runnable you pass to the Thread constructor is called the target or the target Runnable.

You can pass a single Runnable instance to multiple Thread objects, so that the same Runnable becomes the target of multiple threads, as follows:

 public class TestThreads {    public static void main (String [] args) {      MyRunnable r = new MyRunnable();      Thread foo = new Thread(r);      Thread bar = new Thread(r);      Thread bat = new Thread(r);    } } 

Giving the same target to multiple threads means that several threads of execution will be running the very same job (and that the same job will be done multiple times).

image from book
Exam Watch

The Thread class itself implements Runnable. (After all, it has a run() method that we were overriding.) This means that you could pass a Thread to another Thread's constructor:

 Thread t = new Thread(new MyThread()); 

This is a bit silly, but it's legal. In this case, you really just need a Runnnable, and creating a whole other Thread is overkill.

image from book

Besides the no-arg constructor and the constructor that takes a Runnable (the target, i.e., the instance with the job to do), there are other overloaded constructors in class Thread. The constructors we care about are

  • Thread()

  • Thread(Runnable target)

  • Thread(Runnable target, String name)

  • Thread(String name)

You need to recognize all of them for the exam! A little later, we'll discuss some of the other constructors in the preceding list.

So now you've made yourself a Thread instance, and it knows which run() method to call. But nothing is happening yet. At this point, all we've got is a plain old Java object of type Thread. It is not yet a thread of execution. To get an actual thread—a new call stack—we still have to start the thread.

When a thread has been instantiated but not started (in other words, the start() method has not been invoked on the Thread instance), the thread is said to be in the new state. At this stage, the thread is not yet considered to be alive. Once the start() method is called, the thread is considered to be alive (even though the run() method may not have actually started executing yet). A thread is considered dead (no longer alive) after the run() method completes. The isAlive() method is the best way to determine if a thread has been started but has not yet completed its run() method. (Note: The getstate() method is very useful for debugging, but you won't have to know it for the exam.)

Starting a Thread

You've created a Thread object and it knows its target (either the passed-in Runnable or itself if you extended class Thread). Now it's time to get the whole thread thing happening—to launch a new call stack. It's so simple it hardly deserves its own subheading:

 t.start(); 

Prior to calling start() on a Thread instance, the thread (when we use lowercase t, we're referring to the thread of execution rather than the Thread class) is said to be in the new state as we said. The new state means you have a Thread object but you don't yet have a true thread. So what happens after you call start()? The good stuff:

  • A new thread of execution starts (with a new call stack).

  • The thread moves from the new state to the runnable state.

  • When the thread gets a chance to execute, its target run() method will run.

Be sure you remember the following: You start a Thread, not a Runnable. You call start() on a Thread instance, not on a Runnable instance. The following example demonstrates what we've covered so far—defining, instantiating, and starting a thread:

 class FooRunnable implements Runnable {    public void run() {       for(int x =1; x < 6; x++) {         System.out.println("Runnable running");       }    } } public class TestThreads {    public static void main (String [] args) {      FooRunnable r = new FooRunnable();      Thread t = new Thread(r);      t.start();    } } 

Running the preceding code prints out exactly what you'd expect:

 % java TestThreads Runnable running Runnable running Runnable running Runnable running Runnable running 

(If this isn't what you expected, go back and re-read everything in this objective.)

image from book
Exam Watch

There's nothing special about the run() method as far as Java is concerned. Like main(), it just happens to be the name (and signature) of the method that the new thread knows to invoke. So if you see code that calls the run() method on a Runnable (or even on a Thread instance), that's perfectly legal. But it doesn't mean the run() method will run in a separate thread! Calling a run() method directly just means you're invoking a method from whatever thread is currently executing, and the run() method goes onto the current call stack rather than at the beginning of a new call stack. The following code does not start a new thread of execution:

 Runnable r = new Runnable(); r.run();  // Legal, but does not start a separate thread 

image from book

So what happens if we start multiple threads? We'll run a simple example in a moment, but first we need to know how to print out which thread is executing. We can use the getName() method of class Thread, and have each Runnable print out the name of the thread executing that Runnable object's run() method. The following example instantiates a thread and gives it a name, and then the name is printed out from the run() method:

 class NameRunnable implements Runnable {    public void run() {        System.out.println("NameRunnabl.e running");        System.out.println("Run by "          + Thread.currentThread().getName());    } } public class NameThread {    public static void main (String [] args) {      NameRunnable nr = new NameRunnable();      Thread t = new Thread(nr);      t.setName("Fred");      t.start();    } } 

Running this code produces the following, extra special, output:

 % java NameThread NameRunnable running Run by Fred 

To get the name of a thread you call—who would have guessed—getName() on the Thread instance. But the target Runnable instance doesn't even have a reference to the Thread instance, so we first invoked the static Thread.currentThread() method, which returns a reference to the currently executing thread, and then we invoked getName() on that returned reference.

Even if you don't explicitly name a thread, it still has a name. Let's look at the previous code, commenting out the statement that sets the thread's name:

 public class NameThread {    public static void main (String [] args) {      NameRunnable nr = new NameRunnable();      Thread t = new Thread(nr);      // t.setName("Fred");      t.start();    } } 

Running the preceding code now gives us

 % java NameThread NameRunnable running Run by Thread-0 

And since we're getting the name of the current thread by using the static Thread.currentThread() method, we can even get the name of the thread running our main code,

 public class NameThreadTwo {    public static void main (String [] args) {      System.out.println("thread is "       + Thread.currentThread().getName());    } } 

which prints out

 % java NameThreadTwo thread is main 

That's right, the main thread already has a namemain. (Once again, what are the odds?) Figure 9-1 shows the process of starting a thread.

image from book
Figure 9-1: Starting a thread

Starting and Running Multiple Threads

Enough playing around here; let's actually get multiple threads going (more than two, that is). We already had two threads, because the main() method starts in a thread of its own, and then t.start() started a second thread. Now we'll do more. The following code creates a single Runnable instance and three Thread instances. All three Thread instances get the same Runnable instance, and each thread is given a unique name. Finally, all three threads are started by invoking start() on the Thread instances.

 class NameRunnable implements Runnable {     public void run() {         for (int x = 1; x <= 3; x++) {             System.out.println("Run by "                     + Thread.currentThread().getName()                     + ", x is " + x);         }     } } public class ManyNames {     public static void main(String [] args) {         // Make one Runnable         NameRunnable nr = new NameRunnable();         Thread one = new Thread(nr);         Thread two = new Thread(nr);         Thread three = new Thread(nr);         one.setName("Fred");         two.setName("Lucy");         three.setName("Ricky");         one.start();         two.start();         three.start();     } } 

Running this code might produce the following:

 % java ManyNames Run by Fred, x is 1 Run by Fred, x is 2 Run by Fred, x is 3 Run by Lucy, x is 1 Run by Lucy, x is 2 Run by Lucy, x is 3 Run by Ricky, x is 1 Run by Ricky, x is 2 Run by Ricky, x is 3 

Well, at least that's what it printed when we ran it—this time, on our machine. But the behavior you see above is not guaranteed. This is so crucial that you need to stop right now, take a deep breath, and repeat after me, "The behavior is not guaranteed." You need to know, for your future as a Java programmer as well as for the exam, that there is nothing in the Java specification that says threads will start running in the order in which they were started (in other words, the order in which start() was invoked on each thread). And there is no guarantee that once a thread starts executing, it will keep executing until it's done. Or that a loop will complete before another thread begins. No siree Bob. Nothing is guaranteed in the preceding code except this:

Each thread will start, and each thread will run to completion.

Within each thread, things will happen in a predictable order. But the actions of different threads can mix together in unpredictable ways. If you run the program multiple times, or on multiple machines, you may see different output. Even if you don't see different output, you need to realize that the behavior you see is not guaranteed. Sometimes a little change in the way the program is run will cause a difference to emerge. Just for fun we bumped up the loop code so that each run() method ran the for loop 400 times rather than 3, and eventually we did start to see some wobbling:

 public void run() {     for (int x = 1; x <= 400; x++) {         System.out.println("Run by "                 + Thread.currentThread().getName()                 + ", x is " + x);     } } 

Running the preceding code, with each thread executing its run loop 400 times, started out fine but then became nonlinear. Here's just a snip from the command-line output of running that code. To make it easier to distinguish each thread, we put Fred's output in italics and Lucy's in bold, and left Ricky's alone:

  • Run by Fred, x is 345

  • Run by Lucy, x is 337

  • Run by Ricky, x is 310

  • Run by Lucy, x is 338

  • Run by Ricky, x is 311

  • Run by Lucy, x is 339

  • Run by Ricky, x is 312

  • Run by Lucy, x is 340

  • Run by Ricky, x is 313

  • Run by Lucy, x is 341

  • Run by Ricky, x is 314

  • Run by Lucy, x is 342

  • Run by Ricky, x is 315

  • Run by Fred, x is 346

  • Run by Lucy, x is 343

  • Run by Fred, x is 347

  • Run by Lucy, x is 344

  • it continues on

Notice that there's not really any clear pattern here. If we look at only the output from Fred, we see the numbers increasing one at a time, as expected:

  • Run by Fred, x is 345

  • Run by Fred, x is 346

  • Run by Fred, x is 347

And similarly if we look only at the output from Lucy, or Ricky. Each one individually is behaving in a nice orderly manner. But together—chaos! In the fragment above we see Fred, then Lucy, then Ricky (in the same order we originally started the threads), but then Lucy butts in when it was Fred's turn. What nerve! And then Ricky and Lucy trade back and forth for a while until finally Fred gets another chance. They jump around like this for a while after this. Eventually (after the part shown above) Fred finishes, then Ricky, and finally Lucy finishes with a long sequence of output. So even though Ricky was started third, he actually completed second. And if we run it again, we'll get a different result. Why? Because it's up to the scheduler, and we don't control the scheduler! Which brings up another key point to remember: Just because a series of threads are started in a particular order doesn't mean they'll run in that order. For any group of started threads, order is not guaranteed by the scheduler. And duration is not guaranteed. You don't know, for example, if one thread will run to completion before the others have a chance to get in or whether they'll all take turns nicely, or whether they'll do a combination of both. There is a way, however, to start a thread but tell it not to run until some other thread has finished. You can do this with the join() method, which we'll look at a little later.

A thread is done being a thread when its target run() method completes.

When a thread completes its run() method, the thread ceases to be a thread of execution. The stack for that thread dissolves, and the thread is considered dead. Not dead and gone, however, just dead. It's still a Thread object, just not a thread of execution. So if you've got a reference to a Thread instance, then even when that Thread instance is no longer a thread of execution, you can still call methods on the Thread instance, just like any other Java object. What you can't do, though, is call start() again.

Once a thread has been started, it can never be started again.

If you have a reference to a Thread, and you call start(), it's started. If you call start() a second time, it will cause an exception (an IllegalThreadStateException, which is a kind of RuntimeException, but you don't need to worry about the exact type). This happens whether or not the run() method has completed from the first start() call. Only a new thread can be started, and then only once. A runnable thread or a dead thread cannot be restarted.

So far, we've seen three thread states: new, runnable, and dead. We'll look at more thread states before we're done with this chapter.

The Thread Scheduler

The thread scheduler is the part of the JVM (although most JVMs map Java threads directly to native threads on the underlying OS) that decides which thread should run at any given moment, and also takes threads out of the run state. Assuming a single processor machine, only one thread can actually run at a time. Only one stack can ever be executing at one time. And it's the thread scheduler that decides which thread—of all that are eligible—will actually run. When we say eligible, we really mean in the runnable state.

Any thread in the runnable state can be chosen by the scheduler to be the one and only running thread. If a thread is not in a runnable state, then it cannot be chosen to be the currently running thread. And just so we're clear about how little is guaranteed here:

The order in which runnable threads are chosen to run is not guaranteed.

Although queue behavior is typical, it isn't guaranteed. Queue behavior means that when a thread has finished with its "turn," it moves to the end of the line of the runnable pool and waits until it eventually gets to the front of the line, where it can be chosen again. In fact, we call it a runnable pool, rather than a runnable queue, to help reinforce the fact that threads aren't all lined up in some guaranteed order.

Although we don't control the thread scheduler (we can't, for example, tell a specific thread to run), we can sometimes influence it. The following methods give us some tools for influencing the scheduler. Just don't ever mistake influence for control.

image from book
Exam Watch

Expect to see exam questions that look for your understanding of what is and is not guaranteed! You must be able to look at thread code and determine whether the output is guaranteed to run in a particular way or is indeterminate.

image from book

Methods from the java.lang.Thread Class Some of the methods that can help us influence thread scheduling are as follows:

 public static void sleep(long millis) throws InterruptedException public static void yield() public final void join() throws InterruptedException public final void setPriority(int newPriority) 

Note that both sleep() and join() have overloaded versions not shown here.

Methods from the java.lang.Object Class Every class in Java inherits the following three thread-related methods:

 public final void wait() throws InterruptedException public final void notify() public final void notifyAll() 

The wait() method has three overloaded versions (including the one listed here).

We'll look at the behavior of each of these methods in this chapter. First, though, we're going to look at the different states a thread can be in.




SCJP Sun Certified Programmer for Java 5 Study Guide Exam 310-055
SCJP Sun Certified Programmer for Java 5 Study Guide (Exam 310-055) (Certification Press)
ISBN: 0072253606
EAN: 2147483647
Year: 2006
Pages: 131

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