by Joe Weber
A unique property of Java is its built-in support for threads. Threads allow you to do many things at the same time. If you as a human could only move one arm or leg at a time, you would probably feel fairly limited. Threads are the computer's answer to this problem. This chapter covers how threads can be used in Java programs.
Think about a typical corporation. In almost every company there are at least three interdependent departments: management, accounting, and manufacturing/sales. For an efficient company to run, all three of these operations need to work at the same time. If accounting fails to do its job, the company will go bankrupt. If management fails, the company will simply fall apart, and if manufacturing doesn't do its job, the company will have nothing with which to make money.
Many software programs operate under the same conditions as your company. In a company, you complete all the tasks at the same time by assigning them to different people. Each person goes off and does his or her appointed task. With software, you (usually) only have a single processor, and that single processor has to take on the tasks of all these groups. To manage this, a concept called multitasking was invented. In reality, the processor is still only doing one thing at any one time, but it switches between them so fast that it seems like it is doing them all simultaneously. Fortunately, modern computers work much faster than human beings, so you hardly even notice that this is happening.
Now, let's go one step further. Have you ever noticed that the accounting person is really doing more than one thing? For instance, that person spends time photocopying spreadsheets, calculating how many widgets the company needs to sell in order to corner the widget market, adding up all the books, and making sure the bills get paid.
In operating system terms, this is what is known as multithreading. Think about it in this way: Each program is assigned a particular person to carry out a group of tasks, called a process. That person then breaks up his or her time even further into threads.
So, you're saying to yourself, "Why should I care how the computer works, so long as it runs my programs?" Multithreading is important to understand because one of the great advances Java makes over its fellow programming languages is that at the very heart of the language is support for threading. By using threading, you can avoid long pauses between what your users do and when they see things happen. Better yet, you can send tasks such as printing off into the background where users don't have to worry about them--they can continue typing their dissertation or perform some other task.
In Java, currently the most common use of a thread is to allow your applet to go off and do something while the browser continues to do its job. Any application you're working on that requires two things to be done at the same time is probably a great candidate for threading.
You can make your applications and classes run in separate threads in two ways:
It should be noted that making your class able to run as a thread does not automatically make it run as such. A section later in this chapter explains this.
You can make your class runnable as a thread by extending the class java.lang.Thread. This gives you direct access to all the thread methods directly:
public class GreatRace extends Thread
Usually, when you want to make a class able to run in its own thread, you also want to extend the features of some other class. Because Java doesn't support multiple inheritance, the solution to this is to implement the Runnable interface. In fact, Thread actually implements Runnable itself. The Runnable interface has only one method: run(). Any time you make a class implement Runnable, you need to have a run() method in your class. It is in the run() method that you actually do all of the work you want to have done by that particular thread:
public class GreatRace extends java.applet.Applet implements Runnable
Now that you have seen how to make your class runnable, let's take a look at a thread example. The source code for two classes follows (see Listings 13.1 and 13.2):
import goodFrame; import java.awt.Graphics; import java.awt.GridLayout; import Threader; public class GreatRace extends java.applet.Applet implements Runnable{ Threader theRacers[]; static int racerCount = 3; Thread theThreads[]; Thread thisThread; static boolean inApplet=true; int numberofThreadsAtStart; public void init(){ //we will use this later to see if all our Threads have died. numberofThreadsAtStart = Thread.activeCount(); //Specify the layout. We will be adding all of the racers one on top //of the other. setLayout(new GridLayout(racerCount,1)); //Specify the number of racers in this race, and make the arrays for the //Threaders and the actual threads the proper size. theRacers = new Threader [racerCount]; theThreads = new Thread[racerCount]; //Create a new Thread for each racer, and add it to the panel. for (int x=0;x<racerCount;x++){ theRacers[x]=new Threader ("Racer #"+x); theRacers[x].resize(size().width,size().height/racerCount); add (theRacers[x]); theThreads[x]=new Thread(theRacers[x]); } } public void start(){ //Start all of the racing threads for (int x=0;x<racerCount;x++) theThreads[x].start(); //Create a thread of our own. We will use this to monitor the state of //the racers and determine when we should quit altogether. thisThread= new Thread (this); thisThread.start(); } public void stop(){ thisThread.stop(); } public void run(){ //Loop around until all of the racers have finished the race. while(Thread.activeCount()>numberofThreadsAtStart+2){ try{ thisThread.sleep(100); } catch (InterruptedException e){ System.out.println("thisThread was interrupted"); } } //Once the race is done, end the program. if (inApplet){ stop(); destroy(); } else System.exit(0); } public static void main (String argv[]){ inApplet=false; //Check to see if the number of racers has been specified on the command line. if (argv.length>0) racerCount = Integer.parseInt(argv[0]); //Create a new frame and place the race in it. goodFrame theFrame = new goodFrame("The Great Thread Race"); GreatRace theRace = new GreatRace(); theFrame.resize(400,200); theFrame.add ("Center",theRace); theFrame.show(); theRace.init(); theFrame.pack(); theRace.start(); } }//end class GreatRace.
import java.awt.Graphics; import java.awt.Color; public class Threader extends java.awt.Canvas implements Runnable { int myPosition =0; String myName; int numberofSteps=600; //Constructor for a Threader. We need to know our name when we //create the Threader. public Threader (String inName){ myName=new String (inName); } public synchronized void paint(Graphics g){ //Draw a line for the `racing line'. g.setColor (Color.black); g.drawLine (0,size().height/2,size().width,size().height/2); //Draw the round racer. g.setColor (Color.yellow); g.fillOval((myPosition*size().width/numberofSteps),0,15,size().height); } public void run(){ //Loop until we have finished the race. while (myPosition <numberofSteps){ //Move ahead one position. myPosition++; repaint(); //Put ourselves to sleep so the paint thread can get around to painting. try{ Thread.currentThread().sleep(10); }catch (Exception e){System.out.println("Exception on sleep");} } System.out.println("Threader:"+myName+" has finished the race"); } }//end class Threader.
Most of the code in Threader.java and GreatRace.java should be fairly easy for you to understand by now. Let's take a look at the key sections of the code that deal with the actual threads. The first one to look at is the for loop in the init() method of GreatRace (see Listing 13.3).
for (int x=0;x<racerCount;x++){ theRacers[x]=new Threader ("Racer #"+x); theRacers[x].resize(size().width,size().height/racerCount); add (theRacers[x]); theThreads[x]=new Thread(theRacers[x]); }
In the for loop, the first thing to do is create an instance of the class Threader. As you can see from Listing 13.2, Threader is an ordinary class that happens to also implement the Runnable interface. After an instance of Threader is created, it is added to the panel, and the new Thread is created with your Threader argument. Don't confuse the Threader class with the Thread Class.
CAUTION:
The new Thread can only be created using an object extending Thread or one which implements Runnable. In either case, the object must have a run() method. However, when you first create the thread, the run() method is not called. That only happens when the Thread is start()ed.
public void start(){ //Start all of the racing threads. for (int x=0;x<racerCount;x++) // start() will call the run() method. theThreads[x].start(); //Create a thread of our own. We will use this to monitor the state of //the racers and determine when we should quit altogether. thisThread= new Thread (this); thisThread.start(); }
The first task is to start up all the threads created in the init() method. When the thread is started, it calls the run() method right away. In this case, it will be the run() method of the Threader object that was passed to the constructor back in the init() method.
Notice that once the racers have started, a thread is created for the actual applet. This thread will be used to monitor what is going on with all the threads. If the race finishes, you might as well end the program.
Finally, take a look at the last set of important code--the run() method of Threader (see Listing 13.5).
public void run(){ //Loop until we have finished the race. while (myPosition <numberofSteps){ //Move ahead one position. myPosition++; repaint(); //Put ourselves to sleep so the paint thread can get around to painting. try{ Thread.currentThread().sleep(10); }catch (Exception e){System.out.println("Exception on sleep");} } System.out.println("Threader:"+myName+" has finished the race"); }
Notice that the while loop is fairly long. run() is only called once when the thread is started. If you plan to do a lot of repetitive work--which is usually the case in a thread--you need to stay within the confines of run(). In fact, it isn't a bad idea to think of the run() method as being a lot like typical main() methods in other structured languages.
Look down a few lines and notice that you put the thread to sleep a bit, in the middle of each loop (Thread.currentThread().sleep(10)). This is a very important task. You should put your threads to sleep once in a while. This prevents other threads from going into starvation.
It is true that under Windows you can get away without doing this in some cases. This works under Windows because Windows doesn't really behave like it should with respect to the priority of a Thread, as discussed later in the section "A Word About Thread Priority, Netscape, and Windows." However, this is a bad idea, and it probably will not be portable. UNIX machines in particular will look like the applet has hung, and the Macintosh will do the same thing. This has to do with the priority assigned to the paint thread, but there are a lot of other reasons to give the system a breather from your thread.
To better understand the importance of putting a Thread to sleep, it is important to first understand how it is that a computer actually performs threading. How does a computer handle Threads so that it seems to us that it is doing more than one thing at a time? The answer lies at the heart of what is known as task swapping.
Inside a computer is a periodic clock. For this example, say that the clock ticks
every millisecond (in reality, the period is probably much shorter). Now, every millisecond
the computer looks at its process table. In the table are pointers to each of the
processes (and threads) that are currently running. It then checks to see if there
are any threads that want to run, and if not goes back to the one it was previously
running. This is shown in the timeline of Figure 13.1.
FIG. 13.1
With only one process running, the Task Manager always goes back to that process.
If the Task Manager looks at the process table and there are more threads that
are not sleeping, it then goes round-robin between them if they are the same priority.
This activity is shown in Figure 13.2.
FIG. 13.2
With two processes of the same priority running, the Task Manager swaps between them.
The third option that the Task Manager might find is that there are two threads
running, but process 2 is of a lower priority than process 1. In this case, the Task
Manager runs only the thread that is the higher priority. The timeline for this session
is shown in Figure 13.3.
FIG. 13.3
The Task Manager always returns to the higher priority thread (1) until it decides
to go to sleep.
Go ahead and compile the GreatRace, and run it as shown in Figure 13.4 by typing
java GreatRace
You can also access it using your browser, by opening the index.html file at http://www.megastar.com/que/Threads/.
You just saw three rather boring ovals run across the screen. Did you notice that they all ran at almost the same speed, yet they were really all processing separately? You can run the GreatRace with as many racers as you want by typing
java GreatRace 5
The racers should all make it across the screen in about the same time (see Figure
13.5).
FIG. 13.4
GreatRace runs as an application.
FIG. 13.5
GreatRace as an applet.
If you run the race a number of times, you see that the race is actually quite
fair, and each of the racers wins just about an equal number of times. If you show
the Java Console under Netscape (choose Options, Show _Java Console) or look
at the window you ran Java GreatRace from, you can actually see the order in which
the racers finish, as shown in Figure 13.6.
FIG. 13.6
A window shows GreatRace and the DOS window it was run from.
There are two methods in java.lang.Thread that deal with the priority of a thread:
Let's see what happens when you tell the computer you want it to treat each of the racers a bit differently by changing the priority.
Change the init() method in GreatRace.java by adding the following line into the for loop:
theThreads[x].setPriority(Thread.MIN_PRIORITY+x);
The for loop now looks like Listing 13.6.
for (int x=0;x<racerCount;x++){ theRacers[x]=new Threader ("Racer #"+x); theRacers[x].resize(size().width,size().height/racerCount); add (theRacers[x]); theThreads[x]=new Thread(theRacers[x]); theThreads[x].setPriority(Thread.MIN_PRIORITY+x); }
Recompile the GreatRace now, and run it again, as shown in Figure 13.7.
FIG. 13.7
The New GreatRace shown as it is run-- mid-race.
By changing the priority of the racers, all of a sudden the bottom racer always wins. Why? The highest priority thread always gets to use the processor when it is not sleeping. This means that every 10ms, the bottom racer always gets to advance towards the finish line, stopping the work of the other racers. The other racers get a chance to try to catch up only when that racer decides to go to sleep. Unlike the hare in the fable about the tortoise and the hare, though, the highest priority thread always wakes up in 10ms, and rather quickly outpaces the other racers all the way to the finish line. As soon as that racer finishes, the next racer becomes the highest priority and gets to move every 10ms, leaving the next racer further behind.
NOTE: The priority of the thread was changed with the method setPriority(int) from Thread. Note that you did not just give it a number. The priority was set relative to the MIN_PRIORITY variable in Thread. This is a very important step. The MIN_PRIORITY and MAX_PRIORITY are variables that could be set differently for a particular machine. Currently, the MIN_PRIORITY on all machines is 1, and the MAX_PRIORITY is 10. It is important not to exceed these values. Doing so will cause an IllegalArgumentException to be thrown.
If you ran the updated version of the GreatRace under Windows, you saw something
like Figure 13.8. No doubt you're wondering why your race did not turn out the same
as was shown in Figure 13.7. The trailing two racers stayed very close together until
the first one won.
FIG. 13.8
The New GreatRace as it appears running under Windows 95.
With Netscape under Windows as shown in Figure 13.9, you may even be wondering
why your last racer didn't even win!
FIG. 13.9
New GreatRace run as an applet running under Windows 95.
The reason for this discrepancy is that threads under Windows don't have nearly the amount of control in terms of priority as do threads under UNIX or Macintosh machines. In fact, threads that have nearly the same priority are treated almost as if they had the same priority with the Windows version of Netscape. That is the reason why under Netscape the last two racers seem to have a nearly equal chance at winning the race. To make the last racer always win, you must increase the priority difference. Try changing the line in the GreatRace init() method to read like this:
theThreads[x].setPriority(Thread.MIN_PRIORITY+x*2);
Now if you try the race under Windows 95, the last racer should always win by
a good margin, as seen in Figure 13.10.
FIG. 13.10
GreatRace with increased priorities under Windows 95.
If you run it again under Netscape, the last racer also wins, but just barely
(see Figure 13.11).
FIG. 13.11
GreatRace with increased priorities as an applet under Windows 95.
This difference is very important to realize. If you're going to depend on the priority of your threads, make sure that you test the application on both a Windows and Macintosh or UNIX machine. If you don't have the luxury of a UNIX machine or Macintosh, it seems that running the program as a Java application instead of a Java applet is a closer approximation to how the thread priorities should be handled, as you saw in the last two figures.
CAUTION:
These thread priority differences make it very dangerous to not put your threads to sleep occasionally if you're only using a Windows 95 machine. The paint thread, which is a low-priority thread, will get a chance at the processor under Windows, but only because it will be able to keep up just as the racers did. However, this does not work under a Macintosh or UNIX machine.
When dealing with multiple threads, consider this: What happens when two or more threads want to access the same variable at the same time, and at least one of the Threads wants to change the variable? If they were allowed to do this at will, chaos would reign. For example, while one thread reads Joe Smith's record, another thread tries to change his salary (Joe has earned a 50-cent raise). The problem is that this little change causes the Thread reading the file in the middle of the other update to see something somewhat random, and it thinks Joe has gotten a $500 raise. That's a great thing for Joe, but not such a great thing for the company, and probably a worse thing for the programmer who will lose his job because of it. How do you resolve this?
The first thing to do is declare the method that will change the data and the method that will read to be synchronized. Java's keyword, synchronized, tells the system to put a lock around a particular method. At most, one thread may be in any synchronized method at a time. Listing 13.7 shows an example of two synchronized methods.
public synchronized void setVar(int){ myVar=x; } public synchronized int getVar (){ return myVar; }
Now, while in setVar() the JVM sets a condition lock, and no other thread will be allowed to enter a synchronized method, including getVar(), until setVar() has finished. Because the other threads are prevented from entering getVar(), no thread will obtain information which is not correct because setVar() is in mid-write.
Don't make all your methods synchronized or you won't be able to do any multithreading at all, because the other threads will wait for the lock to be released and only one thread will be active at a time. But even with only a couple of methods declared as synchronized, what happens when one thread starts a synchronized method, stops execution until some condition happens that needs to be set by another Thread, and that other Thread would itself have to go into a (blocked) synchronized method? The solution lies in the dining philosopher's problem.
What is the dining philosopher's problem? Well, I won't go into all the details, but let me lay out the scenario for you.
Five philosophers are sitting around a table with a plate of food in front of them. One chopstick (or fork) lies on the table between each philosopher, for a total of five chopsticks. What happens when they all want to eat? They need two chopsticks to eat the food, but there are not enough chopsticks to go around. At most, two of them can eat at any one time--the other three will have to wait. How do you make sure that each philosopher doesn't pick up one chopstick, and none of them can get two? This will lead to starvation because no one will be able to eat. (The philosophers are too busy thinking to realize that one of them can go into the kitchen for more chopsticks; that isn't the solution.)
There are a number of ways to solve this ancient problem (at least in terms of the life of a computer). I won't even try to solve this problem for you. But it's important to realize the consequences. If you make a method synchronized, and it is going to stop because of some condition that can only be set by another thread, make sure you exit the method and return the chopstick to the table. If you don't, it is famine waiting to happen. The philosopher won't return his chopstick(s) to the table, and he will be waiting for something to happen that can't happen because his fellow thinkers don't have utensils to be able to start eating.
Threads have a number of possible states. Let's take a look at how to change the state and what the effects are. The methods covered here are:
start() and stop() are relatively simple operations for a thread. start() tells a thread to start the run() method of its associated Runnable object.
stop() tells the thread to stop. Now really there is more that goes into stop()--it actually throws a ThreadDeath object at the thread. Under almost every situation, you should not try to catch this object. The only time you need to consider doing so is if you have a number of extraordinary things you need to clean up before you can stop.
CAUTION:
If you catch the ThreadDeath object, be sure to throw it again. If you don't do this, the thread will not actually stop and, because the error handler won't notice this, nothing will ever complain.
The sleep() method comes in two varieties. The first is sleep(long), which tells the interpreter that you want to go to sleep for a certain number of milliseconds:
thisThread.sleep(100);
The only problem with this version is that a millisecond, while only an instant for humans, is an awfully long time for a computer. Even on a 486/33 computer, this is enough time for the processor to do 25,000 instructions. On high-end workstations, hundreds of thousands of instructions can be done in one millisecond.
As a result, there is a second incantation: sleep(long,int). With this version of the sleep command, you can put a thread to sleep for a number of milliseconds, plus a few nanoseconds:
thisThread.sleep(99,250);
suspend() and resume() are two methods that you can use to put threads to sleep until some other event has occurred. One such example would be if you were about to start a huge mathematical computation, such as finding the millionth prime number, and you don't want the other threads to be taking up any of the processor until the answer had been computed. (Incidentally, if you're really trying to find the millionth prime number, I would suggest you write the program in a language other than Java, and get yourself a very large computer.)
yield() works a bit differently than suspend(). yield() is much closer to sleep(), in that with yield() you're telling the interpreter that you would like to get out of the way of the other threads, but when they are done, you would like to pick back up. yield() does not require a resume() to start backup when the other threads have stopped, gone to sleep, or died.
The last method to change a thread's running state is destroy(). In general, don't use destroy(). destroy() does not do any cleanup on the thread. It just destroys it. Because it is essentially the same as shutting down a program in progress, you should use destroy() only as a last resort.
Java.lang.Thread has one method that deals with determining the number of threads that are running: activeCount().
Thread.activeCount() returns the integer number of the number of threads that are running in the current ThreadGroup. This is used in the GreatRace to find out when all of the threads have finished executing. Notice that in the init() method, you check the number of threads that are running when you start your program. In the run() method, you then compare this number plus 2 to the number of threads that are currently running to see if your racers have finished the race:
while(Thread.activeCount()>numberofThreadsAtStart+2){
Why add +2? You need to account for two additional threads that do not exist before the race starts. The first one is made out of GreatRace(thisThread) which actually runs through the main loop of GreatRace. The other thread that has not started up at the point the init() method is hit is the Screen_Updater thread. This thread does not start until it is required to do something.
TIP: As with most programming solutions, you have many ways to determine if all the racers have finished. You can use thread messaging with PipedInputStream and PipedOutputStream, or check to see if the threads are alive.
Sometimes it's necessary to be able to see all the threads that are running. For instance, what if you did not know that there were two threads you needed to account for in the main() loop of the GreatRace? There are three methods in java.lang.Thread that help you show just this information:
enumerate(Thread[]) is used to get a list of all the threads that are running in the current ThreadGroup. getName() is used to get the name assigned to the thread, while its counterpart setName(String) is used to actually set this name. By default, if you do not pass in a name for the Thread to the constructor of a thread, it is assigned the default name Thread-x where x is a unique number for that thread.
Let's modify the GreatRace a bit to show all the threads that are running. Change the run() method to look like what's shown in Listing 13.8.
public void run(){ Thread allThreads[]; //Loop around until all of the racers have finished the race. while(Thread.activeCount()>1){ try{ //Create a Thread array for allThreads. allThreads = new Thread[Thread.activeCount()]; //Obtain a link to all of the current Threads. Thread.enumerate (allThreads); //Display the name of all the Threads. System.out.println("****** New List ***** "); for (int x=0;x<allThreads.length;x++) System.out.println("Thread:"+allThreads[x].getName()+": "+allThreads[x].getPriority()+":"+allThreads[x].isDaemon()); thisThread.sleep(1000); } catch (InterruptedException e){ System.out.println("thisThread was interrupted"); } } //Once the race is done, end the program. if (inApplet){ stop(); destroy(); } else System.exit(0); }
The new set of lines are at the very beginning of the while() loop. These lines create an array of threads, use the enumerate method which was just talked about, and write out the name of each of the threads to System.out.
Now recompile the program and run it. Under Netscape, make sure you show the Java
Console by choosing Options, Show _Java Console (see Figure 13.12).
FIG. 13.12
The GreatRace running under Netscape with the Java Console showing.
As the race progresses and each of the racers completes the race, you can see that the number of active threads does really decrease. In fact, run the application and give it a number higher than three (see Figure 13.13). In other words, try:
java GreatRace 5
FIG. 13.13
GreatRace can be run with five racers.
Threads can be one of two types: either a thread is a user thread or a Daemon thread.
So what is a Daemon? Well, Webster's Dictionary says it is "a supernatural being or force, not specifically evil."
In a sense, Webster's is right, even with respect to Daemon threads. While the thread is not actually supernatural and it is definitely not evil, a Daemon thread is not a natural thread, either. You can set off Daemon threads on a path without ever worrying whether they come back. Once you start a Daemon thread, you don't need to worry about stopping it. When the thread reaches the end of the tasks it was assigned, it stops and changes its state to inactive, much like user threads.
A very important difference between Daemon threads and user threads is that Daemon threads can run all the time. If the Java interpreter determines that only Daemon threads are running, it will exit, without worrying if the Daemon threads have finished. This is very useful because it enables you to start threads that do things such as monitoring; they die on their own when there is nothing else running.
The usefulness of this technique is limited for graphical Java applications because, by default, several base threads are not set to be Daemon. These include:
Unfortunately, this means that any application using the AWT class will have non-daemon threads that prevent the application from exiting.
Two methods in java.lang.Thread deal with the Daemonic state assigned to a thread:
The first method, isDaemon(), is used to test the state of a particular thread. Occasionally, this is useful to an object running as a thread so it can determine if it is running as a Daemon or a regular thread. isDaemon() returns true if the thread is a Daemon, and false otherwise.
The second method, setDaemon(boolean), is used to change the Daemonic state of the thread. To make a thread a Daemon, you indicate this by setting the input value to true. To change it back to a user thread, you set the Boolean value to false.
If you had wanted to make each of the racers in the GreatRace Daemon threads, you could have done so. In the init() for loop, this would have looked like Listing 13.9.
for (int x=0;x<racerCount;x++){ theRacers[x]=new Threader ("Racer #"+x); theRacers[x].resize(size().width,size().height/racerCount); add (theRacers[x]); theThreads[x]=new Thread(theRacers[x]); theThreads[x].setDaemon(true); }