Notice: This material is excerpted from Special Edition Using Java, ISBN: 0-7897-0604-0. The electronic version of this material has not been through the final proof reading stage that the book goes through before being published in printed form. Some errors may exist here that are corrected before the book is published. This material is provided "as is" without any warranty of any kind.
by Joseph L. Weber
A unique property of Java is its built-in support for threads. This chapter covers how threads are used in Java applications.
In this chapter, you will learn
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 be done 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 get all the tasks done 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 deeper. 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, and 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, your 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 the user doesn't have to worry about them-the user can continue typing his or her dissertation or perform some other task.
In Java, currently the most common utilization 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, by extending the Thread class or by implementing the Runnable interface. 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 will also want to extend the features of some other class. Because Java doesn't support multiple inheritance, the solution to this is to implement Runnable. In fact Thread actually implements Runnable itself. The Runnable interface has only one method: run(). Anytime you make a class implement Runnable, you will 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
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 them wants to change it? 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 $0.50 raise). The problem is that this little change causes the thread reading the file in the middle of the 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?
Well, the first thing to do is to declare the method that will change the data and the method that will read to be synchronized. Java's key word, 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 25.1 shows an example of two synchronized methods.
Listing 25.1 Two synchronized methods public synchronized void setVar(int){ myVar=x; } public synchronized int getVar (){ return myVar; }
Don't just make all your methods synchronized or you won't be able to do any multithreading at all. But even with only a couple of methods declared as synchronized, what happens when one thread starts a synchronized method, and then stops until some condition that needs to be set by another thread? The solution lies in the dining philosopher's problem.
What is the dinning philosophers 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 5 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 thinker doesn't have utensils to be able to start eating.
Now that you have looked at the fundamental philosophies, such as synchronization and making your class runnable lets take a look at a thread example. The source code for two classes follows GreatRace, a class that adds several items of the class Threader. Threader operates in its own thread and races along a track to the finish line.
Listing 25.2 GreatRace.java 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 //Theaders 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 all together 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 interupted"); } } //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
Listing 25.3 Threader.java 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.
Listing 25.4 for loop from init() in GreatRace 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 to create an instance of the class Threader. As you can see from the previous page, 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 new thread is created with using our Threader. Don't confuse the Threader class with the Thread Class. Threader is the name of the class we created in listing 25.3..
The new Thread can only be created from an object extending thread or an object that 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 will happen later.
The next important set of code is in the start() method, again of GreatRace.java.
Listing 25.5 start() method of GreatRace 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 all together thisThread= new Thread (this); thisThread.start(); }
The first task is 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 Threadable that was passed to the constructor back in the init() method.
Notice that once the racers are all 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.
Listing 25.x run() method of Threadable (racer) 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 you start a fairly long loop in the run() method. 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 the 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 you will 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 always 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 we will discuss later. 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.
Go ahead and compile the GreatRace, and run it by typing
java GreatRace Fig. 25.1 GreatRace run as an application
You can also access it using your browser, by opening the index.html file.
Fig. 25.2 Great Race as an applet
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.
If you run the race a number of times you will 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 (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
the race.
Fig. 25.3 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. These are:
getPriority is used to obtain the current priority of a thread, and setPriority is used to set a new priority for a thread.
Now, 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 this:
Listing 25.x new for loop for init() method 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.
Fig. 25.4 New Great Race run - mid race
By changing the priority on the Racers, now all of a sudden the bottom Racer always wins. The reason for this is that 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 story about the tortoise and the hare though, the highest priority thread always wakes back up in 10ms, and rather quickly out paces the other racers all the way to the finish. As soon as that racer finishes though, the next racer becomes the highest priority and gets to move every 10ms, leaving the next racer further behind.
To change the priority of the thread, the method setPriority(int) from Thread was used. 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, no doubt you're wondering why your race did not turn out the same as it showed before. The trailing two racers stayed very close together until the first one won.
Fig. 25.5 New Great Race under Windows 95
If you ran it with Netscape under Windows, you may even be wondering why your last racer didn't even win!
Fig. 25.6 New Great Race run as an Applet 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 under Netscape. That is the reason that 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*3);
Now if you try the race under Windows 95, the last racer should always win by a good margin
Fig. 25.7 Great Race with increased priorities under Windows 95
If you run the same thing under Netscape, the last racer will also still win, but just barely.
Fig. 25.8 Great Race with increased priorities as an Applet under Window 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 under Windows and on a Macintosh or UNIX machine. If you don't have the luxury of a UNIX machine or a 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.
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 will not work under a Macintosh or UNIX machine.
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(). stop() actually throws a ThreadDeath object at the thread. Under almost every situation, you should not try to catch this object. The only time you will need to consider doing so will be if you have a number of extraordinary things you need to clean up before you can stop.
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.
You have already briefly looked at the sleep method, when putting the Threadable's to sleep in the GreatRace was talked about. Putting a thread to sleep essentially tells the Virtual Machine that "I'm done with what I am doing right now, wake me back up in a little while". By putting a thread to sleep you are allowing lower priority threads a chance to get a shot at the processor. This is especially important when there are very low priority threads that are doing tasks that, while not as important, still need to be done periodically. Without stepping out of the way occasionally, your thread can put these threads into starvation.
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);
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 1 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 pairs of threads that you can use to put the thread 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. 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 very large computer.
yield() works a bit differently than suspend(). yield() is much closer to sleep(), in that with yield you're telling the interperter 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 threads running state is destroy(). In general, don't use destroy(). destroy() does not do any clean up 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. This is activeCount().
Thread.activeCount() returns an 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 +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 +2? You need to account for two additional threads that do not exist before the race is started. The first one is the thread which 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.
As with most programming solutions, there are 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 two methods in java.lang.Thread that help us 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 in the constructor of a thread, it is assigned the default name Thread-x where x is a unique number for that thread.
Lets modify the GreatRace a bit to show all the threads that are running. Change the run() method to look like this:
Listing 25.8 new run() method for GreatRace 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, utilize the enumerate method which was just talked about, and write out the name of each of the threads to System.out.
Go ahead and recompile the program and run it. Under Netscape,
make sure you show the Java Console (Properties, Show Java
Console).
Fig. 25.9 Greate Race under Netscape with the Java Console showing.
As the race progresses and each of the racers completes the race, you will be able to see that the number of active threads does really decrease. In fact, run the application and give it a number higher than three. In other words, try:
Fig. 25.10 Great Race run with 5 racers
Threads can be one of two types. Either a thread is a user thread or a Daemon thread.
What is a Daemon any way? Well, Webster's Dictionary says it is "a supernatural being or force, not specifically evil." In a sense, Webster's is right. 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 will stop and change its state to being inactive.
A very important difference between Daemon threads and user threads though is that Deamon Threads can run all the time. If the Java interpretor determines that only Daemon threads are running, it will exit, without worrying if the Daemon threads have finished. This is very useful because it allows you to start threads that do things such as monitoring; they will 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 AWT-Input, AWT-Motif, Main, and Screen_Updater. Unfortunately, this means that any application using the AWT class will have non-daemon threads that will prevent the application from exiting.
Two methods in java.lang.Thread deal with the Daemonic state assigned to a thread. They are:
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 as a regular thread. isDaemon() will return 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 25.x:
Listing 25.x new for loop for init() method in GreatRace.java 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);
}
For technical support for our books and software contact support@mcp.com
Copyright ©1996, Que Corporation