This chapter is about threadswhat they are and how they can make your applets work better with other applets and with the Java system in general. We discuss 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, and how the scheduler works in Java.
First, lets 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. Whats 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 youre 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. Wouldnt 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 Web 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 thingsand moreby using the built-in threading of the Java language.
Depending on your experience with operating systems and with environments within those systems, you may or may not have run into the concept of threads. Lets start from the beginning with some definitions.
When a program runs, it starts executing, runs its initialization code, calls methods or procedures, and continues running and processing until its complete or until the program is exited. That program uses a single threadwhere the thread is a single locus of control for the program.
Multithreading, as in Java, enables several different execution threads to run at the same time inside the same program, in parallel, without interfering with each other.
Heres a simple example. Suppose you have a long computation near the start of a programs execution. This long computation may not be needed until later on in the programsexecutionits actually tangential to the main point of the program, but it needs to get done eventually. In a single-threaded program, you have to wait for that computation to finish before the rest of the program can continue running. In a multithreaded system, you can put that computation into its own thread, enabling the rest of the program to continue running independently.
Using threads in Java, you can create an applet so that it runs in its own thread, and it will happily run all by itself without interfering with any other part of the system. Using threads, you can have lots of applets running at once on the same page. Depending on how many you have, you may eventually exhaust the system so that all of them will run slower, but all of them will run independently.
Even if you dont have lots of applets, using threads in your applets is good Java programming practice. The general rule of thumb for well-behaved applets: Whenever you have any bit of processing that is likely to continue for a long time (such as an animation loop, or a bit of code that takes a long time to execute), put it in a thread.
How do you create an applet that uses threads? There are several things you need to do. Fortunately, none of them are difficult, and a lot of the basics of using threads in applets is just boilerplate code that you can copy and paste from one applet to another. Because its so easy, theres almost no reason not to use threads in your applets, given the benefits.
There are four modifications you need to make to create an applet that uses threads:
The first change is to the first line of your class definition. Youve already got something like this:
public class MyAppletClass extends java.applet.Applet { ... }
You need to change it to the following :
public class MyAppletClass extends java.applet.Applet implements Runnable { ... }
What does this do? It includes support for the Runnable interface in your applet. Interfaces, as discussed in Chapter 14, Classes, Packages, and Interfaces, are a way to collect method names common to different classes, which can then be mixed in and implemented inside different classes that need to implement that behavior. Here, the Runnable interface includes the behavior your applet needs to run a thread; in particular, it gives you a default definition for the run() method.
The second step is to add an instance variable to hold this applets thread. Call it anything you like; its a variable of the type Thread (Thread is a class in java.lang, so you dont have to import it):
Thread runner;
Third, add a start() method or modify the existing one so that it does nothing but create a new thread and start it running. Heres a typical example of a start() method:
public void start() { if (runner == null); { runner = new Thread(this); runner.start(); } }
If you modify start() to do nothing but spawn a thread, where does the body of your applet go? It goes into a new method, run(), which looks like this:
public void run() { // what your applet actually does }
run() can contain anything you want to run in the separate thread: initialization code, the actual loop for your applet, or anything else that needs to run in its own thread. You also can create new objects and call methods from inside run(), and theyll also run inside that thread. The run method is the real heart of your applet.
Finally, now that youve got threads running and a start method to start them, you should add a stop() method to suspend execution of that thread (and therefore whatever the applet is doing at the time) when the reader leaves the page. stop(), like start(), is usually something along these lines:
public void stop() { if (runner != null) { runner.stop(); runner = null; } }
The stop() method here does two things: it stops the thread from executing and also sets the threads variable (runner) to null. Setting the variable to null makes the Thread object it previously contained available for garbage collection so that the applet can be removed from memory after a certain amount of time. If the reader comes back to this page and this applet, the start method creates a new thread and starts up the applet once again.
And thats it! Four basic modifications, and now you have a well-behaved applet that runs in its own thread.
If threading is so wonderful, why doesnt 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 youll see later in this chapter), 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 environmentthe 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.
Getting used to threads takes a little while and a new way of thinking. Rather than imagining that you always know exactly whats happening when you look at a method youve 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. Lets 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. (Dont be too concerned about the specifics of 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 enables 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.
Its even worse than you might think for unsynchronized variables. Because the compiler can keep them around in registers during computations, and a threads registers cant be seen by other threads (especially if theyre 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 haveproduced 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 its needed and does not use registers.
The method howMany() in the last example doesnt 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, thoughsomeone who uses the value returned from the method. Heres an example:
public class Point { // redefines class Point from package java.awt private float x, y; // OK since were 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 points 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 itselfand 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 dont 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).
You could try to make a safe version of print() by simply adding the synchronized keyword modifier to it, but instead, lets 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 points 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.
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 points x is + safeX + y is + safeY); } }
Now youve 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. Youre still assuming, however, that the Point p has properly protected itself. (You can always assume this about system classesbut 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: Youve 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 be concerned with synchronization. |
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 callers 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 objects 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). You can fully relax when you know for certain that youre the only one that knows about an object. Of course, if you created the object yourself and gave it to no one else, you can be that certain.
Finally, suppose you want a class variable to collect some information across all a classs 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 its 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 thisan instance.) Luckily, you already know the tools you need to solve this:
public class StaticCounter { private static int crucialValue; public void countMe() { synchronized(getClass()) { // cant directly name StaticCounter crucialValue += 1; // the (shared) class is now locked } } }
The trick is to lock on a different objectnot 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 shouldnt 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.for.Name(StaticCounter)) { StaticCounter.crucialValue += 1; }
NOTE |
---|
The direct use of another classs (or objects) variable is really bad styleits 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.
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 youre 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 youre 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 dont 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 itand 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 isnt running t.resume();
A thread will automatically suspend() and then resume() when its first blocked at a synchronized point and then later unblocked (when its that threads turn to run).
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? Interfaces 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. 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. Heres 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 dont need to talk to the Thread explicitly or to the instance of MySecondThread, heres a one line shortcut:
new Thread(new MySecondThread()).start();
NOTE |
---|
As you can see, the class name, MySecondThread, is a bit of a misnomerit 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. |
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. Dont 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. aSRs 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. Thats why when adding synchronization, you need to be so careful 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 (ThreadTesters main()) to create the thread, its run() method needs another way to get its hands on that thread. Often, youll 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.
CAUTION |
---|
You can do some reasonably disastrous things with your knowledge of threads. For example, if youre running in the main thread of the system and, because you think you are in a different thread, you accidentally say the following:
it has unfortunate consequences for your (soon-to-be-dead) program! |
The example then calls getName() on the current thread to get the threads 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 Im planning to wait forever for you to finish your run() method. You dont want to do this lightly: If you have anything else important you need to get done in your thread any time soon, you cant 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 didnt have anything else you wanted to do while waiting for join() anyway.) Heres 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.
If you want your threads to have particular names, you can assign them yourself by using a two-argument form of Threads 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. Heres 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. Its also useful, for debugging purposes, to give threads meaningful names (such as network input) so that when they appear during an errorin a stack trace, for exampleyou 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).
Lets 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 its sentbut look at Threads declaration of the stop() method:
public final void stop() { . . . }
The final here means that you cant 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 } } }
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 thats fine; you posited that the cleanup was needed only when stop() was used on the thread.
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.
NOTE |
---|
The part of the system that decides the real-time ordering of threads is called the scheduler. |
Normally, any scheduler has two fundamentally different ways of looking at its job: nonpreemptive scheduling and preemptive time-slicing.
NOTE |
---|
With nonpreemptive 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. |
Nonpreemptive 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. Youve given a little too much control to the scheduler. The final touch many modern schedulers add is to enable 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 behaviorssome behaving more like a preemptive scheduler, and some more like a nonpreemptive 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 nonpreemptive 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, theres another thread thats 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 nonpreemptive, and would never normally run the second thread.
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 shouldnt depend on these values; use the class variables, or tricks like the one shown in the preceding 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 twopotato 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 its 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.
Heres 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. Another thread, however, might interrupt the sleeping 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.
This chapter showed that parallelism is desirable and powerful, but introduces many new problemssuch as methods and variables now need to be protected from thread conflictsthat 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 nonpreemptive 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. Dont be afraid to experiment; you can92't permanently break anything, and you only learn by trying.