Tuesday 6 March 2012

Multithreading

  • Multithreading
    • The Problem with Parallelism
    • Thinking Multithreaded
      • Points About Points
        • TryAgainPointPrinter
        • SafePointPrinter
        • ReallySafePoint
      • Protecting a Class Variable
    • Creating and Using Threads
      • The Runnable Interface
      • ThreadTester
      • NamedThreadTester
    • Knowing When a Thread has Stopped
    • Thread Scheduling
      • Preemptive Versus Nonpreemptive
      • Testing Your Scheduler
        • PriorityThreadTester
    • Summary
    • Q&A


Multithreading

by Charles L. Perkins
Today, you'll learn more about the threads mentioned briefly in Week 2:
  • How to "think multithreaded"
  • How to protect your methods and variables from unintended thread conflicts
  • How to create, start, and stop threads and threaded classes
  • How the scheduler works in Java
First, let's begin by motivating the need for threads.
Threads are a relatively recent invention in the computer science world. Although processes, their larger parent, have been around for decades, threads have only recently been accepted into the mainstream. What's odd about this is that they are extremely valuable, and programs written with them are noticeably better, even to the casual user. In fact, some of the best individual, Herculean efforts over the years have involved implementing a threads-like facility by hand to give a program a more friendly feel to its users.
Imagine that you're using your favorite text editor on a large file. When it starts up, does it need to examine the entire file before it lets you edit? Does it need to make a copy of the file? If the file is huge, this can be a nightmare. Wouldn't it be nicer for it to show you the first page, enabling you to begin editing, and somehow (in the background) complete the slower tasks necessary for initialization? Threads allow exactly this kind of within-the-program parallelism.
Perhaps the best example of threading (or lack of it) is a WWW browser. Can your browser download an indefinite number of files and Web pages at once while still enabling you to continue browsing? While these pages are downloading, can your browser download all the pictures, sounds, and so forth in parallel, interleaving the fast and slow download times of multiple Internet servers? HotJava can do all of these things—and more—by using the built-in threading of the Java language.

The Problem with Parallelism

If threading is so wonderful, why doesn't every system have it? Many modern operating systems have the basic primitives needed to create and run threads, but they are missing a key ingredient. The rest of their environment is not thread-safe. Imagine that you are in a thread, one of many, and each of you is sharing some important data managed by the system. If you were managing that data, you could take steps to protect it (as you'll see later today), but the system is managing it. Now visualize a piece of code in the system that reads some crucial value, thinks about it for a while, and then adds 1 to the value:
if (crucialValue > 0) {

    . . .                 // think about what to do

    crucialValue += 1;

}
Remember that any number of threads may be calling upon this part of the system at once. The disaster occurs when two threads have both executed the if test before either has incremented the crucialValue. In that case, the value is clobbered by them both with the same crucialValue + 1, and one of the increments has been lost. This may not seem so bad to you, but imagine instead that the crucial value affects the state of the screen as it is being displayed. Now, unfortunate ordering of the threads can cause the screen to be updated incorrectly. In the same way, mouse or keyboard events can be lost, databases can be inaccurately updated, and so forth.
This disaster is inescapable if any significant part of the system has not been written with threads in mind. Therein lies the barrier to a mainstream threaded environment—the large effort required to rewrite existing libraries for thread safety. Luckily, Java was written from scratch with this is mind, and every Java class in its library is thread-safe. Thus, you now have to worry only about your own synchronization and thread-ordering problems, because you can assume that the Java system will do the right thing.

Atomic operations are operations that appear to happen "all at once"—exactly at the same time—to other threads.


Note: Some readers may wonder what the fundamental problem really is. Can't you just make the ... area in the example smaller and smaller to reduce or eliminate the problem? Without atomic operations, the answer is no. Even if the ... took zero time, you must first look at the value of some variable to make any decision and then change something to reflect that decision. These two steps can never be made to happen at the same time without an atomic operation. Unless you're given one by the system, it's literally impossible to create your own.

Even the one line crucialValue += 1 involves three steps: get the current value, add one to it, and store it back. (Using ++crucialValue doesn't help either.) All three steps need to happen "all at once" (atomically) to be safe. Special Java primitives, at the lowest levels of the language, provide you with the basic atomic operations you need to build safe, threaded programs.

Thinking Multithreaded

Getting used to threads takes a little while and a new way of thinking. Rather than imagining that you always know exactly what's happening when you look at a method you've written, you have to ask yourself some additional questions. What will happen if more than one thread calls into this method at the same time? Do you need to protect it in some way? What about your class as a whole? Are you assuming that only one of its methods is running at the same time?
Often you make such assumptions, and a local instance variable will be messed up as a result. Let's make a few mistakes and then try to correct them. First, the simplest case:
public class  ThreadCounter {

    int  crucialValue;

    public void  countMe() {

        crucialValue += 1;

    }

    public int   howMany() {

        return crucialValue;

    }

}
This code suffers from the most pure form of the "synchronization problem:" the += takes more than one step, and you may miscount the number of threads as a result. (Don't worry about how threads are created yet, just imagine that a whole bunch of them are able to call countMe(), at once, at slightly different times.) Java allows you to fix this:
public class  SafeThreadCounter {

    int  crucialValue;

    public synchronized void  countMe() {

        crucialValue += 1;

    }

    public              int   howMany() {

        return crucialValue;

    }

}
The synchronized keyword tells Java to make the block of code in the method thread safe. Only one thread will be allowed inside this method at once, and others have to wait until the currently running thread is finished with it before they can begin running it. This implies that synchronizing a large, long-running method is almost always a bad idea. All your threads would end up stuck at this bottleneck, waiting single file to get their turn at this one slow method.
It's even worse than you might think for unsynchronized variables. Because the compiler can keep them around in registers during computations, and a thread's registers can't be seen by other threads (especially if they're on another processor in a true multiprocessor computer), a variable can be updated in such a way that no possible order of thread updates could have produced the result. This is completely incomprehensible to the programmer. To avoid this bizarre case, you can label a variable volatile, meaning that you know it will be updated asynchronously by multiprocessor-like threads. Java then loads and stores it each time it's needed and does not use registers.

Note: In earlier releases, variables that were safe from these bizarre effects were labeled threadsafe. Because most variables are safe to use, however, they are now assumed to be thread-safe unless you mark them volatile. Using volatile is an extremely rare event. In fact, in the 1.0 release, the Java library does not use volatile anywhere.

Points About Points

The method howMany() in the last example doesn't need to be synchronized, because it simply returns the current value of an instance variable. Someone higher in the call chain may need to be synchronized, though—someone who uses the value returned from the method. Here's an example:
public class  Point {      //redefines class Point from package java.awt

    private float  x, y;   //OK since we're in a different package here

    public  float  x() {        // needs no synchronization

        return x;

    }

    public  float  y() {        // ditto

        return y;

    }

    . . .    // methods to set and change x and y

}

public class  UnsafePointPrinter {

    public void  print(Point p) {

        System.out.println("The point's x is " + p.x()

                                + " and y is " + p.y() + ".");

    }

}
The analogous methods to howMany() are x() and y(). They need no synchronization, because they just return the values of instance variables. It is the responsibility of the caller of x() and y() to decide whether it needs to synchronize itself—and in this case, it does. Although the method print() simply reads values and prints them out, it reads two values. This means that there is a chance that some other thread, running between the call to p.x() and the call to p.y(), could have changed the value of x and y stored inside the Point p. Remember, you don't know how many other threads have a way to reach and call methods in this Point object! "Thinking multithreaded" comes down to being careful any time you make an assumption that something has not happened between two parts of your program (even two parts of the same line, or the same expression, such as the string + expression in this example).
TryAgainPointPrinter
You could try to make a safe version of print() by simply adding the synchronized keyword modifier to it, but instead, let's try a slightly different approach:
public class  TryAgainPointPrinter {

    public void  print(Point p) {

        float  safeX, safeY;

        synchronized(this) {

            safeX = p.x();     // these two lines now

            safeY = p.y();     // happen atomically

        }

        System.out.print("The point's x is " + safeX

                                  + " y is " + safeY);

    }

}
The synchronized statement takes an argument that says what object you would like to lock to prevent more than one thread from executing the enclosed block of code at the same time. Here, you use this (the instance itself), which is exactly the object that would have been locked by the synchronized method as a whole if you had changed print() to be like your safe countMe() method. You have an added bonus with this new form of synchronization: you can specify exactly what part of a method needs to be safe, and the rest can be left unsafe.
Notice how you took advantage of this freedom to make the protected part of the method as small as possible, while leaving the String creations, concatenations, and printing (which together take a small but nonzero amount of time) outside the "protected" area. This is both good style (as a guide to the reader of your code) and more efficient, because fewer threads get stuck waiting to get into protected areas.
SafePointPrinter
The astute reader, though, may still be worried by the last example. It seems as if you made sure that no one executes your calls to x() and y() out of order, but have you prevented the Point p from changing out from under you? The answer is no, you still have not solved the problem. You really do need the full power of the synchronized statement:
public class  SafePointPrinter {

    public void  print(Point p) {

        float  safeX, safeY;

        synchronized(p) {     // no one can change p

            safeX = p.x();    // while these two lines

            safeY = p.y();    // are happening atomically

        }

        System.out.print("The point's x is " + safeX

                                  + " y is " + safeY);

    }

}
Now you've got it. You actually needed to protect the Point p from changes, so you lock it by giving it as the argument to your synchronized statement. Now when x() and y() happen together, they can be sure to get the current x and y of the Point p, without any other thread being able to call a modifying method between. You're still assuming, however, that the Point p has properly protected itself. (You can always assume this about system classes—but you wrote this Point class.) You can make sure by writing the only method that can change x and y inside p yourself:
public class  Point {

    private float  x, y;

    . . .        // the x() and y() methods

    public synchronized void  setXAndY(float  newX,  float  newY) {

        x = newX;

        y = newY;

    }

}
By making the only "set" method in Point synchronized, you guarantee that any other thread trying to grab the Point p and change it out from under you has to wait: you've locked the Point p with your synchronized(p) statement, and any other thread has to try to lock the same Point p via the implicit synchronized(this) statement p now executes when entering setXAndY(). Thus, at last, you are thread-safe.

Note: By the way, if Java had some way of returning more than one value at once, you could write a synchronized getXAndY() method for Points that returns both values safely. In the current Java language, such a method could return a new, unique Point to guarantee to its callers that no one else has a copy that might be changed. This sort of trick can be used to minimize the parts of the system that need to worry about synchronization.

ReallySafePoint
An added benefit of the use of the synchronized modifier on methods (or of synchronized(this) {. . .}) is that only one of these methods (or blocks of code) can run at once. You can use that knowledge to guarantee that only one of several crucial methods in a class will run at once:
public class  ReallySafePoint {

    private float  x, y;

    public synchronized Point  getUniquePoint() {

        return new Point(x, y);    // can be a less safe Point

    }                              // because only the caller has it

    public synchronized void   setXAndY(float  newX,  float  newY) {

        x = newX;

        y = newY;

    }

    public synchronized void   scale(float  scaleX,  float  scaleY) {

        x *= scaleX;

        y *= scaleY;

    }

    public synchronized void   add(ReallySafePoint  aRSP) {

        Point  p = aRSP.getUniquePoint();

        x += p.x();

        y += p.y();

    }   // Point p is soon thrown away by GC; no one else ever saw it

}
This example combines several of the ideas mentioned previously. To avoid a caller's having to synchronize(p) whenever getting your x and y, you give them a synchronized way to get a unique Point (like returning multiple values). Each method that modifies the object's instance variables is also synchronized to prevent it from running between the x and y references in getUniquePoint() and from stepping on one another as they modify the local x and y. Note that add() itself uses getUniquePoint() to avoid having to say synchronized(aRSP).
Classes that are this safe are a little unusual; it is more often your responsibility to protect yourself from other threads' use of commonly held objects (such as Points). Only when you know for certain that you're the only one that knows about an object, can you fully relax. Of course, if you created the object yourself and gave it to no one else, you can be that certain.

Protecting a Class Variable

Finally, suppose you want a class variable to collect some information across all a class's instances:
public class  StaticCounter {

    private static int  crucialValue;

    public synchronized void  countMe() {

        crucialValue += 1;

    }

}
Is this safe? If crucialValue were an instance variable, it would be. Because it's a class variable, however, and there is only one copy of it for all instances, you can still have multiple threads modifying it by using different instances of the class. (Remember, the synchronized modifier locks the object this—an instance.) Luckily, you already know the tools you need to solve this:
public class  StaticCounter {

    private static int  crucialValue;

    public void  countMe() {

        synchronized(getClass()) {   // can't directly name StaticCounter

            crucialValue += 1;       // the (shared) class is now locked

        }

    }

}
The trick is to "lock" on a different object—not on an instance of the class, but on the class itself. Because a class variable is "inside" a class, just as an instance variable is inside an instance, this shouldn't be all that unexpected. In a similar way, classes can provide global resources that any instance (or other class) can access directly by using the class name, and lock by using that same class name. In the last example, crucialValue was used from within an instance of StaticCounter, but if crucialValue were declared public instead, from anywhere in the program, it would be safe to say the following:
synchronized(Class.forName("StaticCounter")) {

    StaticCounter.crucialValue += 1;

}

Note: The direct use of another classes (object's) variable is really bad style—it's used here simply to demonstrate a point quickly. StaticCounter would normally provide a countMe()-like class method of its own to do this sort of dirty work.

You can now begin to appreciate how much work the Java team has done for you by thinking all these hard thoughts for each and every class (and method!) in the Java class library.

Creating and Using Threads

Now that you understand the power (and the dangers) of having many threads running at once, how are those threads actually created?

Caution: The system itself always has a few so-called daemon threads running, one of which is constantly doing the tedious task of garbage collection for you in the background. There is also a main user thread that listens for events from your mouse and keyboard. If you're not careful, you can sometimes lock up this main thread. If you do, no events are sent to your program and it appears to be dead. A good rule of thumb is that whenever you're doing something that can be done in a separate thread, it probably should be. Threads in Java are relatively cheap to create, run, and destroy, so don't use them too sparingly.

Because there is a class java.lang.Thread, you might guess that you could create a thread of your own by subclassing it—and you are right:
public class  MyFirstThread extends Thread { // a.k.a., java.lang.Thread

    public void  run() {

        . . .              // do something useful

    }

}
You now have a new type of Thread called MyFirstThread, which does something useful (unspecified) when its run() method is called. Of course, no one has created this thread or called its run() method, so it does absolutely nothing at the moment. To actually create and run an instance of your new thread class, you write the following:
MyFirstThread  aMFT = new MyFirstThread();

aMFT.start();    // calls our run() method
What could be simpler? You create a new instance of your thread class and then ask it to start running. Whenever you want to stop the thread, you use this:
aMFT.stop();    
Besides responding to start() and stop(), a thread can also be temporarily suspended and later resumed:
Thread  t = new Thread();

t.suspend();

. . .         // do something special while t isn't running

t.resume();
A thread will automatically suspend() and then resume() when it's first blocked at a synchronized point and then later unblocked (when it's that thread's "turn" to run).

The Runnable Interface

This is all well and good if every time you want to create a Thread you have the luxury of being able to place it under the Thread class in the single-inheritance class tree. What if it more naturally belongs under some other class, from which it needs to get most of its implementation? The interfaces of Day 16 come to the rescue:
public class  MySecondThread extends ImportantClass implements Runnable {

    public void  run() {

        . . .              // do something useful

    }

}
By implementing the interface Runnable, you declare your intention to run in a separate thread. In fact, the class Thread itself implements this interface, as you might expect from the design discussions on Day 16. As you also might guess from the example, the interface Runnable specifies only one method: run(). As in MyFirstThread, you expect someone to create an instance of a thread and somehow call your run() method. Here's how this is accomplished:
MySecondThread  aMST = new MySecondThread();

Thread          aThread = new Thread(aMST);

aThread.start();   // calls our run() method, indirectly
First, you create an instance of MySecondThread. Then, by passing this instance to the constructor making the new Thread, you make it the target of that Thread. Whenever that new Thread starts up, its run() method calls the run() method of the target it was given (assumed by the Thread to be an object that implements the Runnable interface). When start() is called, aThread (indirectly) calls your run() method. You can stop aThread with stop(). If you don't need to talk to the Thread explicitly or to the instance of MySecondThread, here's a one line shortcut:
new Thread(new MySecondThread()).start();

Note: As you can see, the class name, MySecondThread, is a bit of a misnomer—it does not descend from Thread, nor is it actually the thread that you start() and stop(). It probably should have been called MySecondThreadedClass or ImportantRunnableClass.

ThreadTester

Here's a longer example:
public class  SimpleRunnable implements Runnable {

    public void  run() {

        System.out.println("in thread named '"

                             + Thread.currentThread().getName() + "'");

    }  // any other methods run() calls are in current thread as well

}

public class  ThreadTester {

    public static void  main(String argv[]) {

        SimpleRunnable  aSR = new SimpleRunnable();

        while (true) {

            Thread  t = new Thread(aSR);

            System.out.println("new Thread() " + (t == null ?

                                             "fail" : "succeed") + "ed.");

            t.start();

            try { t.join(); } catch (InterruptedException ignored) { }


                         // waits for thread to finish its run() method

        }

    }

}

Note: You may be worried that only one instance of the class SimpleRunnable is created, but many new Threads are using it. Don't they get confused? Remember to separate in your mind the aSR instance (and the methods it understands) from the various threads of execution that can pass through it. aSR's methods provide a template for execution, and the multiple threads created are sharing that template. Each remembers where it is executing and whatever else it needs to make it distinct from the other running threads. They all share the same instance and the same methods. That's why you need to be so careful, when adding synchronization, to imagine numerous threads running rampant over each of your methods.

The class method currentThread() can be called to get the thread in which a method is currently executing. If the SimpleRunnable class were a subclass of Thread, its methods would know the answer already (it is the thread running). Because SimpleRunnable simply implements the interface Runnable, however, and counts on someone else (ThreadTester's main()) to create the thread, its run() method needs another way to get its hands on that thread. Often, you'll be deep inside methods called by your run() method when suddenly you need to get the current thread. The class method shown in the example works, no matter where you are.

Warning: You can do some reasonably disastrous things with your knowledge of threads. For example, if you're running in the main thread of the system and, because you think you are in a different thread, you accidentally say the following:

hread.currentThread().stop();

it has unfortunate consequences for your (soon-to-be-dead) program!

The example then calls getName() on the current thread to get the thread's name (usually something helpful, such as Thread-23) so it can tell the world in which thread run() is running. The final thing to note is the use of the method join(), which, when sent to a thread, means "I'm planning to wait forever for you to finish your run() method." You don't want to do this lightly: if you have anything else important you need to get done in your thread any time soon, you can't count on how long the join()ed thread may take to finish. In the example, its run() method is short and finishes quickly, so each loop can safely wait for the previous thread to die before creating the next one. (Of course, in this example, you didn't have anything else you wanted to do while waiting for join() anyway.) Here's the output produced:
new Thread() succeeded.

in thread named 'Thread-1'

new Thread() succeeded.

in thread named 'Thread-2'

new Thread() succeeded.

in thread named 'Thread-3'

^C
Ctrl+C was pressed to interrupt the program, because it otherwise would continue forever.

NamedThreadTester

If you want your threads to have particular names, you can assign them yourself by using a two-argument form of Thread's constructor:
public class  NamedThreadTester {

    public static void  main(String argv[]) {

        SimpleRunnable  aSR = new SimpleRunnable();

        for (int  i = 1;  true;  ++i) {

            Thread  t = new Thread(aSR, "" + (100 - i) 

                                           + " threads on the wall...");

            System.out.println("new Thread() " + (t == null ?

                                             "fail" : "succeed") + "ed.");

            t.start();

            try { t.join(); } catch (InterruptedException ignored) { }

        }

    }

}
which takes a target object, as before, and a String, which names the new thread. Here's the output:
new Thread() succeeded.

in thread named '99 threads on the wall...'

new Thread() succeeded.

in thread named '98 threads on the wall...'

new Thread() succeeded.

in thread named '97 threads on the wall...'

^C
Naming a thread is one easy way to pass it some information. This information flows from the parent thread to its new child. It's also useful, for debugging purposes, to give threads meaningful names (such as network input) so that when they appear during an error—in a stack trace, for example—you can easily identify which thread caused the problem. You might also think of using names to help group or organize your threads, but Java actually provides you with a ThreadGroup class to perform this function. A ThreadGroup allows you to group threads, to control them all as a unit, and to keep them from being able to affect other threads (useful for security).

Knowing When a Thread has Stopped

Let's imagine a different version of the last example, one that creates a thread and then hands the thread off to other parts of the program. Suppose it would then like to know when that thread dies so that it can perform some cleanup operation. If SimpleRunnable were a subclass of Thread, you might try to catch stop() whenever it's sent—but look at Thread's declaration of the stop() method:
public final void  stop() { . . . }
The final here means that you can't override this method in a subclass. In any case, SimpleRunnable is not a subclass of Thread, so how can this imagined example possibly catch the death of its thread? The answer is to use the following magic:
public class  SingleThreadTester {

    public static void  main(String argv[]) {

        Thread  t = new Thread(new SimpleRunnable());

        try {

            t.start();

            someMethodThatMightStopTheThread(t);

        } catch (ThreadDeath  aTD) {

            . . .          // do some required cleanup

            throw aTD;     // re-throw the error

        }

    }

}
You understand most of this magic from yesterday's lesson. All you need to know is that if the thread created in the example dies, it throws an error of class ThreadDeath. The code catches that error and performs the required cleanup. It then rethrows the error, allowing the thread to die. The cleanup code is not called if the thread exits normally (its run() method completes), but that's fine; you posited that the cleanup was needed only when stop() was used on the thread.

Note: Threads can die in other ways—for example, by throwing exceptions that no one catches. In these cases, stop() is never called, and the previous code is not sufficient. (If the cleanup always has to occur, even at the normal end of a thread's life, you can put it in a finally clause.) Because unexpected exceptions can come out of nowhere to kill a thread, multithreaded programs that carefully catch and handle all their exceptions are more predictable, robust, and easier to debug.

Thread Scheduling

You might wonder exactly what order your threads will be run in, and how you can control that order. Unfortunately, the current implementations of the Java system cannot precisely answer the former, though with a lot of work, you can always do the latter.
The part of the system that decides the real-time ordering of threads is called the scheduler.

Preemptive Versus Nonpreemptive

Normally, any scheduler has two fundamentally different ways of looking at its job: non-preemptive scheduling and preemptive time-slicing.
With non-preemptive scheduling, the scheduler runs the current thread forever, requiring that thread explicitly to tell it when it is safe to start a different thread. With preemptive time-slicing, the scheduler runs the current thread until it has used up a certain tiny fraction of a second, and then "preempts" it, suspend()s it, and resume()s another thread for the next tiny fraction of a second.
Non-preemptive scheduling is very courtly, always asking for permission to schedule, and is quite valuable in extremely time-critical, real-time applications where being interrupted at the wrong moment, or for too long, could mean crashing an airplane.
Most modern schedulers use preemptive time-slicing, because, except for a few time-critical cases, it has turned out to make writing multithreaded programs much easier. For one thing, it does not force each thread to decide exactly when it should "yield" control to another thread. Instead, every thread can just run blindly on, knowing that the scheduler will be fair about giving all the other threads their chance to run.
It turns out that this approach is still not the ideal way to schedule threads. You've given a little too much control to the scheduler. The final touch many modern schedulers add is to allow you to assign each thread a priority. This creates a total ordering of all threads, making some threads more "important" than others. Being higher priority often means that a thread gets run more often (or gets more total running time), but it always means that it can interrupt other, lower-priority threads, even before their "time-slice" has expired.
The current Java release does not precisely specify the behavior of its scheduler. Threads can be assigned priorities, and when a choice is made between several threads that all want to run, the highest-priority thread wins. However, among threads that are all the same priority, the behavior is not well-defined. In fact, the different platforms on which Java currently runs have different behaviors—some behaving more like a preemptive scheduler, and some more like a non-preemptive scheduler.

Note: This incomplete specification of the scheduler is terribly annoying and, presumably, will be corrected in later releases. Not knowing the fine details of how scheduling occurs is perfectly all right, but not knowing whether equal priority threads must explicitly yield or face running forever, is not all right. For example, all the threads you have created so far are equal priority threads, so you don't know their basic scheduling behavior!

Testing Your Scheduler

To find out what kind of scheduler you have on your system, try the following:
public class  RunnablePotato implements Runnable {

    public void  run() {

        while (true)

            System.out.println(Thread.currentThread().getName());

    }

}

public class  PotatoThreadTester {

    public static void  main(String argv[]) {

        RunnablePotato  aRP = new RunnablePotato();

        new Thread(aRP, "one potato").start();

        new Thread(aRP, "two potato").start();

    }

}
For a non-preemptive scheduler, this prints the following:
one potato

one potato

one potato

. . .
forever, until you interrupt the program. For a preemptive scheduler that time-slices, it repeats the line one potato a few times, followed by the same number of two potato lines, over and over:
one potato

one potato

...

one potato

two potato

two potato

...

two potato

. . .
until you interrupt the program. What if you want to be sure the two threads will take turns, no matter what the system scheduler wants to do? You rewrite RunnablePotato as follows:
public class  RunnablePotato implements Runnable {

    public void  run() {

        while (true) {

            System.out.println(Thread.currentThread().getName());

            Thread.yield();  // let another thread run for a while

        }

    }

}

Tip: Normally you would have to say Thread.currentThread().yield() to get your hands on the current thread, and then call yield(). Because this pattern is so common, however, the Thread class provides a shortcut.

The yield() method explicitly gives any other threads that want to run a chance to begin running. (If there are no threads waiting to run, the thread that made the yield() simply continues.) In our example, there's another thread that's just dying to run, so when you now
execute the class ThreadTester, it should output the following:
one potato

two potato

one potato

two potato

one potato

two potato

. . .
even if your system scheduler is non-preemptive, and would never normally run the second thread.
PriorityThreadTester
To see whether priorities are working on your system, try this:
public class  PriorityThreadTester {

    public static void  main(String argv[]) {

        RunnablePotato  aRP = new RunnablePotato();

        Thread          t1  = new Thread(aRP, "one potato");

        Thread          t2  = new Thread(aRP, "two potato");

        t2.setPriority(t1.getPriority() + 1);

        t1.start();

        t2.start();   // at priority Thread.NORM_PRIORITY + 1

    }

}

Tip: The values representing the lowest, normal, and highest priorities that threads can be assigned are stored in class variables of the Thread class: Thread.MIN_PRIORITY, Thread.NORM_PRIORITY, and Thread.MAX_PRIORITY. The system assigns new threads, by default, the priority Thread.NORM_PRIORITY. Priorities in Java are currently defined in a range from 1 to 10, with 5 being normal, but you shouldn't depend on these values; use the class variables, or tricks like the one shown in this example.

If one potato is the first line of output, your system does not preempt using priorities.
Why? Imagine that the first thread (t1) has just begun to run. Even before it has a chance to print anything, along comes a higher-priority thread (t2) that wants to run right away. That higher-priority thread should preempt (interrupt) the first, and get a chance to print two potato before t1 finishes printing anything. In fact, if you use the RunnablePotato class that never yield()s, t2 stays in control forever, printing two potato lines, because it's a higher priority than t1 and it never yields control. If you use the latest RunnablePotato class (with yield()), the output is alternating lines of one potato and two potato as before, but starting with two potato.
Here's a good, illustrative example of how complex threads behave:
public class  ComplexThread extends Thread {

    private int  delay;

    ComplexThread(String  name,  float  seconds) {

        super(name);

        delay = (int) seconds * 1000;   // delays are in milliseconds

        start();                        // start up ourself!

    }

    public void  run() {

        while (true) {

            System.out.println(Thread.currentThread().getName());

            try {

                Thread.sleep(delay);

            } catch (InterruptedException e) {

                return;

            }

        }

    }

    public static void  main(String argv[]) {

        new ComplexThread("one potato",   1.1F);

        new ComplexThread("two potato",   1.3F);

        new ComplexThread("three potato", 0.5F);

        new ComplexThread("four",         0.7F);

    }

}
This example combines the thread and its tester into a single class. Its constructor takes care of naming (itself) and of starting (itself), because it is now a Thread. The main() method creates new instances of its own class, because that class is a subclass of Thread. run() is also more complicated, because it now uses, for the first time, a method that can throw an unexpected exception.
The Thread.sleep() method forces the current thread to yield() and then waits for at least the specified amount of time to elapse before allowing the thread to run again. It might be interrupted, however, while sleeping by another thread. In such a case, it throws an InterruptedException. Now, because run() is not defined as throwing this exception, you must "hide" the fact by catching and handling it yourself. Because interruptions are usually requests to stop, you should exit the thread, which you can do by simply returning from the run() method.
This program should output a repeating but complex pattern of four different lines, where every once in a great while you see the following:
. . .

one potato

two potato

three potato

four

. . .
You should study the pattern output to prove to yourself that true parallelism is going on inside Java programs. You may also begin to appreciate that, if even this simple set of four threads can produce such complex behavior, many more threads must be capable of producing near chaos if not carefully controlled. Luckily, Java provides the synchronization and thread-safe libraries you need to control that chaos.

Summary

Today, you learned that parallelism is desirable and powerful, but introduces many new problems—methods and variables now need to be protected from thread conflicts—that can
lead to chaos if not carefully controlled.
By "thinking multithreaded," you can detect the places in your programs that require synchronized statements (or modifiers) to make them thread-safe. A series of Point examples demonstrated the various levels of safety you can achieve, while ThreadTesters showed how subclasses of Thread, or classes that implement the Runnable interface, are created and run() to generate multithreaded programs.
You also learned how to yield(), how to start(), stop(), suspend(), and resume() your threads, and how to catch ThreadDeath whenever it happens.
Finally, you learned about preemptive and non-preemptive scheduling, both with and without priorities, and how to test your Java system to see which of them your scheduler is using.
This wraps up the description of threads. You now know enough to write the most complex of programs: multithreaded ones. As you get more comfortable with threads, you may begin to use the ThreadGroup class or to use the enumeration methods of Thread to get your hands on all the threads in the system and manipulate them. Don't be afraid to experiment; you can't permanently break anything, and you only learn by trying.

Q&A

Q: If they're so important to Java, why haven't threads appeared throughout the entire book?
A: Actually, they have. Every stand-alone program written so far has "created" at least one thread, the one in which it is running. (Of course the system created that Thread for it automatically.)
Q: How exactly do these threads get created and run? What about applets?
A: When a simple, stand-alone Java program starts up, the system creates a main thread, and its run() method calls your main() method to start your program—you do nothing to get that Thread. Likewise, when a simple applet loads into a Java-capable browser, a Thread has already been created by the browser, and its run() method calls your init() and start() methods to start your program. In either case, a new Thread() of some kind was done somewhere, by the Java environment itself.
Q: The ThreadTester class has an infinite loop that creates Threads and then join()s with them. Is it really infinite?
A: In theory, yes. In actuality, how far the loop runs determines the resource limits of (and tests the stability of) the threads package and garbage collector in your Java release. Over time, all Java releases will converge on making the loop truly infinite.
Q: I know Java releases are still a little fuzzy about the scheduler's behavior, but what's the current story?
A: Here are the gruesome details, relayed by Arthur van Hoff at Sun: the way Java schedules threads "...depends on the platform. It is usually preemptive, but not always time-sliced. Priorities are not always observed, depending on the underlying implementation." This final clause gives you a hint that all this confusion is an implementation problem, and that in some future release, the design and implementation will both be clear about scheduling behavior.
Q: Does Java support more complex multithreaded concepts, such as semaphores?
A: The class Object in Java provides methods that can be used to build up condition variables, semaphores, and any higher-level parallel construct you might need. The method wait() (and its two variants with a timeout) causes the current thread to wait until some condition has been satisfied. The method notify() (or notifyAll()), which must be called from within a synchronized method or block, tells the thread (or all threads) to wake up and check that condition again, because something has changed. By careful combinations of these two primitive methods, any data structure can be manipulated safely by a set of threads, and all the classical parallel primitives needed to implement published parallel algorithms can be built.
Q: My parallel friends tell me I should worry about something called "deadlock." Should I?
A: Not for simple multithreaded programs. However, in more complicated programs, one of the biggest worries does become one of avoiding a situation in which one thread has locked an object and is waiting for another thread to finish, while that other thread is waiting for the first thread to release that same object before it can finish. That's a deadlock—both threads will be stuck forever. Mutual dependencies like this involving more than two threads can be quite intricate, convoluted, and difficult to locate, much less rectify. They are one of the main challenges in writing complex multithreaded programs.

No comments:

Post a Comment