- Special Edition Using Java, 2nd Edition -

Chapter 14

Threads


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 computers answer to this problem. This chapter covers how threads can be used in Java programs.

What Are Threads?

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, 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.

Why Use 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.

How to Make Your Classes Threadable

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.

Extend Thread

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

Implement Runnable

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(). Anytime 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

The Great Thread Race

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 14.1 and 14.2):

Listing 14.1 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
//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 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 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

Listing 14.2 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

Understanding the GreatRace

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 14.3).

Listing 14.3 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 listing 14.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.

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.
 

The next important set of code is in the start() method, again of GreatRace.java (see listing 14.4).

Listing 14.4 start() method of GreatRace.

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 all together
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 14.5).

Listing 14.5 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 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 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 I discuss 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.

Thread Processing

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 14.1.


FIG. 14.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 14.2.


FIG. 14.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 14.3.


FIG. 14.3

The task manager always returns to the higher priority thread (1) until it decides to go to sleep.

Try Out the Great Thread Race

Go ahead and compile the GreatRace, and run it as shown in figure 14.4 by typing


FIG. 14.4

GreatRace runs as an application.

You can also access it using your browser, by opening the index.html file at http://www.megastar.com/que/Threads/.


FIG. 14.5

GreatRace 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

The racers should all make it across the screen in about the same time.

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 14.6.


FIG. 14.6

A window shows GreatRace and the DOS window it was run from.

Changing the Priority

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:

The for loop now looks like listing 14.6.

Listing 14.6 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, as shown in figure 14.7.


FIG. 14.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 out-paces 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.

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.
 

A Word About Thread Priority, Netscape, and Windows

If you ran the updated version of the GreatRace under Windows, you saw something like figure 14.8. No doubt you're wondering why your race did not turn out the same as it shown in figure 14.7. The trailing two racers stayed very close together until the first one won.


FIG. 14.8

The New GreatRace as it appears running under Windows 95.

With Netscape under Windows as shown in figure 14.9, you may even be wondering why your last racer didn't even win!


FIG. 14.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:

Now if you try the race under Windows 95, the last racer should always win by a good margin, as seen in figure 14.10.


FIG. 14.10

GreatRace with increased priorities under Windows 95.

If you run it again under Netscape, the last racer also wins, but just barely (see fig. 14.11).


FIG. 14.11

GreatRace 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 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.

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.
 

Synchronization

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 others 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 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 14.7 shows an example of two synchronized methods.

Listing 14.7 Two synchronized methods.

public synchronized void setVar(int){
myVar=x;
}
public synchronized int getVar (){
return myVar;
}

Now, while in setVar() the Java VM 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, and then 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.

Speaking with a Forked Tongue

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 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 thinker doesn't have utensils to be able to start eating.

Changing the Running State of the Threads

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.

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. Putting a thread to sleep essentially tells the Virtual Machine, "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);

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 pairs of 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 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.

Obtaining the Number of Threads That are Running

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.
 
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.
 

Finding All the Threads That are Running

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 14.8.

Listing 14.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.
u>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 fig. 14.12).


FIG. 14.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 fig. 14.13). In other words, try:


FIG. 14.13

GreatRace can be run with five racers.

The Daemon Property

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

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 14.9.

Listing 14.9 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);
}

 


Previous PageTOCNext Page

| Previous Chapter | Next Chapter |

| Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company.