Section 5.4
Threads and Animation
MUST AN APPLET BE COMPLETELY DEPENDENT on events sent from outside to get anything done? Can't an applet do something on its own initiative and on its own schedule? Something more like a traditional program that just executes a sequence of instructions from beginning to end?
The answer is yes. The applet can create a thread. A thread represents a thread of control, which independently executes a sequence of instructions from beginning to end. Several threads can exist and run at the same time. An applet -- or, indeed, any Java program -- can create one or more threads and start them running. Each of the threads acts like a little independent program, except that threads can communicate with each other.
One common use of threads is to do animation. A thread runs continuously while an applet is displayed. Several times a second, the thread changes the applet's display. If the changes are frequent enough, and the changes small enough, the viewer will perceive continuous motion.
Programming with threads is called parallel programming because several threads can run in parallel. Parallel programming can get tricky when several threads are all using a shared resource. An example of a shared resource would be the screen. If several threads try to draw to the screen at the same time, it's possible for the contents of the screen to become corrupted. Instance variables can also be shared resources. In order to avoid problems with shared resources, access to shared resources must be synchronized in some way. Java defines a fairly natural and easy way of doing such synchronization. I will discuss it below.
Note that on most computers, you can't literally have two threads running "at the same time," since there is only one processor, which can only do one thing at a time. Only computers with more than one processor can literally do more than one thing at a time. However, this does not solve the synchronization problem on single-processor computers! A single-processor computer simulates multiprocessing by switching rapidly from one thread to another. Without proper synchronization, a thread can be interrupted at any time to let another thread run. Suppose, for example that a thread reads the value of an important variable just before it is interrupted. After the thread resumes, it makes a decision based on the value it just read. The problem is that, without proper synchronization, some other thread might have butted in and changed the value of the variable in the meantime! The decision that the thread makes is based on a value that is no longer necessarily valid. Synchronization can be used to make sure this doesn't happen.
Even if an applet creates only one thread, there will still be two threads running in parallel, since there is always a user interface thread that monitors user actions and feeds events to the applet. What happens if the thread is trying to draw something at the same time that the user changes the size of the applet? What if one thread is drawing to an off-screen image at the same time another thread is copying that image to the screen? These are synchronization problems. So, even in the simple case of an apple that creates a single thread, synchronization can be an issue.
In Java, a thread is just an object of type Thread. The class Thread is defined in the standard package java.lang. A thread object must have a subroutine to execute. That subroutine is always named run(), but there are two different places where the run() method might be located, depending on how the thread is programmed.
The Thread class itself defines a run() method, which doesn't do anything. One way to make a useful thread is to define a subclass of Thread and override the run() method in your subclass to make it do something useful.
A second way to program a thread -- and the only one I will use for the time being -- is to create a class that implements the interface called Runnable. This interface defines one method, public void run(). (To implement this interface means to declare that the class "implements Runnable" and to define the method public void run() in the class.) A thread can be constructed from an object of type Runnable. When such a thread is run, it executes the run() method from the Runnable object. I'll give an example of all this in just a moment. The advantage of defining a thread in this way is that the run() method will have access to all the instance variables of the object, so that the thread will be able to read and change the values of those variables. (A disadvantage is that throwing a run() method into a class can completely muddle the clear division of responsibilities that should be the hallmark of object-oriented programming. The run() method is logically part of the Thread object, but it is physically part of the Runnable object. Keep this in mind: It's best to think of the run() method as a separate entity.)
Suppose that runnableObject is an object belonging to a class that implements Runnable. And suppose that runner is a variable of type Thread. Then the statement
runner = new Thread(runnableObject);
creates a thread that can execute the run() method of runnableObject. However, the thread does not automatically start running. To get it to run, you have to call its start() method:
runner.start();
The thread will then begin executing its run() method. At the same time, the rest of your program continues to execute. The thread continues until it reaches the end of the run() method. At that point it dies and cannot be restarted or reused. If you want to execute the same run() method again, you have to create a new thread to do it. You can check whether a thread is still alive by calling its isAlive() method, which returns a boolean value:
if (runner.isAlive()) . . .
(Note: it is possible to kill a thread before it finishes normally by calling its stop() method. However, use of this method is discouraged since it is error-prone, and I will avoid it entirely.)
As we work through a few examples in the rest of this section, I'll be introducing several important new ideas. One of the ideas is managing the state of an applet. The state of an applet consists of its instance variables. The state determines how the applet will respond when its methods are called. Managing the state means determining how and when the state should change. In many cases, it also means making the state of the applet apparent to the user, so the user isn't surprised or frustrated by the applet's behavior. One way to make the state of the applet apparent is to disable a button whenever clicking on that button would make no sense. A button, bttn, can be disabled with the command "bttn.setEnabled(false);", and it can be enabled with "bttn.setEnabled(true);".
Let's look at the first example. When you click on the button in the following applet, a thread is created that blinks the color of the message from red to green and back again several times. Note that the button is disabled while the message is blinking:
The source code for this applet begins with the lines:
public class BlinkingHelloWorld1 extends Applet implements ActionListener, Runnable { ColoredHelloWorldCanvas canvas; // A canvas that displays the message Button blinkBttn; // The "Blink at Me" button. Thread blinker; // A thread that cycles the message colors.In this example, the applet class itself implements Runnable. (There is nothing special about applets in this regard. Any class can implement Runnable. It might be useful, for example, to have a Runnable canvas.) Later in the source code, the thread will be created with the command "blinker = new Thread(this);", where this refers to the applet object itself. This means that the thread we create will execute whatever run() method is defined in the BlinkingHelloWorld1 class. Let's think about what we want this thread to do. (This is just like designing a small program.) We want the thread to blink the color of the message. We'll also give the thread the responsibility of disabling and enabling the button, since that way we can be sure that the button will be disabled just as long as the message is blinking. A pseudocode version of the run method would be:
disable the blinkBttn set the text color to green pause for a while set the text color to red pause for a while set the text color to green pause for a while set the text color to red pause for a while set the text color to green pause for a while set the text color to red enable the blinkBttnThe pauses are necessary since otherwise the blinking would go by much too fast to see. Unfortunately, for technical reasons, getting a thread to pause requires use of a feature of Java that I haven't covered yet, the try...catch statement. Rather than try to explain it here, I will just give you a subroutine that can be called by a thread to insert a pause in its execution:
void delay(int milliseconds) { // Pause for the specified number of milliseconds, // where 1000 milliseconds equal one second. try { Thread.sleep(milliseconds); } catch (InterruptedException e) { } }Using this subroutine and a setTextColor() routine from the ColoredHelloWorldCanvas class, the applet's run() method becomes:
public void run() { blinkBttn.setEnabled(false); canvas.setTextColor(Color.green); delay(300); canvas.setTextColor(Color.red); delay(300); canvas.setTextColor(Color.green); delay(300); canvas.setTextColor(Color.red); delay(300); canvas.setTextColor(Color.green); delay(300); canvas.setTextColor(Color.red); blinkBttn.setEnabled(true); }The blinker thread, which executes this run method, has to be created and started when the user clicks the "Blink at Me!" button. This is done in the applet's actionPerformed() method, which is called when the user clicks the button:
public void actionPerformed(ActionEvent evt) { if (blinker == null || ! blinker.isAlive()) { blinker = new Thread(this); blinker.start(); } }This routine creates and starts a thread. That thread executes the above run() method, which blinks the text several times. When the run method ends, the thread dies.
I only want one of these threads to be running at a time. To be sure of this, before creating the new thread, I test whether another thread already exists and is running. Of course, since the button is disabled while a thread is running, this test might seem unnecessary. However, it's usually better to test a condition rather than just assume it's true. And in this case, it's just possible that the user might manage to click the button a second time before the first thread gets started.
The complete source code for this example can be found in the file BlinkingHelloWorld1.java.
In the previous example, the thread that was created ran for only a short time, until it had run through all the instructions in its run() method. In many cases, though, we want the thread to keep running until it is told to stop. That is, we want the thread to terminate when some external event, not under the control of the thread, occurs. The next example creates a thread that runs until the user clicks on a button, or until the applet itself is stopped for some reason:
When you click on the "Blink!" button, the message starts cycling between red and another color. The name of the "Blink!" button changes to "Stop!". Clicking on the "Stop!" button will stop the blinking. (The command that changes the name of the button, blinkBttn, to "Stop!" is blinkBttn.setLabel("Stop!"). This is another case of changing the appearance of the applet when it changes state in order to help the user understand what is going on.) The other two buttons can be used at any time to set the colors that are used for the blinking message. Recall that the blinking is handled by one thread while the button events are handled by a separate user interface thread, so in this example you really do get to see two threads operating in parallel.
The run() method for this example has a while loop that makes it blink the text over and over. The interesting question is how to get the loop to end and the thread to stop running when the user clicks the "Stop!" button. The answer is to use a shared instance variable for communication between the thread and the applet. The thread tests the value of this variable and keeps running as long as that variable has a certain value. When the user clicks on the "Stop!" button, the applet changes the value of the variable. The thread sees this change and responds by exiting from its run() method and dieing.
I tend to use a variable named status for this type of communication. It's a good idea to use named constants as the possible values of status. For this example, the status information that I need is whether the thread should continue or should end. I use constants named GO and TERMINATE to represent these two possibilities. The thread continues running as long as the value of status is GO. It ends when the value of status changes to TERMINATE. To implement this, the applet includes the following instance variables:
private final static int GO = 0, // Constants for use as value of status. TERMINATE = 1; private volatile int status; // This variable is used for communication between // the applet and the thread. The value is set by // the applet to tell the thread what to do. In // particular, when the applet wants the thread // to terminate, it sets the value of status to // TERMINATE.(The word volatile is a new modifier that has to do with communication between threads. It should be used on a variable whose value is set by one thread and read by another. The somewhat technical reason is this: If a variable is not declared to be volatile, then on some computers, when one thread changes the value of the variable, other threads might not immediately see the new value. This is another type of synchronization problem. Later in this section, I'll mention "synchronized" methods and statements. Variables that are accessed only in synchronized methods and statements don't have to be declared volatile.)
The idea is for the thread to run just so long as the value of status is GO. The thread's run() method tests the value of status in a while loop, which continues only so long as status == GO. Note that the value of status must be equal to GO before the thread is started, since otherwise the run() method would finish immediately. When the user clicks on the "Blink!" button, the following commands are executed to start the thread:
runner = new Thread(this); status = GO; runner.start();In addition to blinking the text, the thread is also responsible for setting the button's label. Here is the entire run() method for the thread:
public void run() { blinkBttn.setLabel("Stop!"); while (status == GO) { waitDelay(300); changeColor(); } blinkBttn.setLabel("Blink!"); }Here, waitDelay() imposes a delay of 300 milliseconds, while changeColor() changes the color of the displayed message. Both these methods are defined elsewhere in the applet.
When the user clicks the "Stop!" button, the value of status must be set to TERMINATE. But we have to be careful. What if the user never clicks on "Stop!"? We should be careful not to leave the thread running after the applet is destroyed. The system tells an applet when it is about to be destroyed by calling its destroy() method. An applet can also be stopped, with the possibility of being restarted later. Before the system stops the applet, it calls its stop() method. An applet is always stopped before it is destroyed. (See Section 1.) An applet that creates threads that might otherwise run forever should make sure to terminate them, either in its stop() or destroy() method.
In the example, I use the stop() method to stop the thread by setting status to TERMINATE. The stop() method will be called by the system if you close the window that displays this page or follow a link to another page (or, in Netscape, if you just resize the page). So, if you start the message blinking, go to another page, and then return to this one, the blinking should be stopped.
In the second blinking hello world applet, there are two reasons why the color of the message might change: because the runner thread decides it's time to change it or because the user clicks on the "Red/Green" or "Red/Blue" button. These two reasons are handled by two different threads. The variable that records the current color and, indeed, the whole display canvas are resources that are shared by two threads. When a thread is accessing a shared resource, it usually needs exclusive access to that resource, so that no other thread can butt in and access the resource at the same time. In Java, exclusive access is implemented using synchronized methods and synchronized statements. A method is declared to be synchronized by adding the synchronized modifier to its definition. Here, for example, is the method from the sample applet that is called by the runner thread to change the color of the message:
synchronized void changeColor() { // Change color from red to alternateColor or vice versa. // The variable isRed records whether the current color is red. if (isRed) { canvas.setTextColor(alternateColor); isRed = false; } else { canvas.setTextColor(Color.red); isRed = true; } }The other method that can change the color of the text is the actionPerformed() method, which is called by the user interface thread when the user clicks on one of the buttons. Like the changeColor() method, the actionPerformed() method is declared to be synchronized. This means that all the code that does color changes is contained inside synchronized methods. Therefore, only one of the color-change processes can be running at any given time, and there is no possibility of their interfering with each other.
Here, briefly, is how such synchronization is implemented in Java: Every object in Java has an associated "lock." The lock can be "held" by a thread, but only one thread can hold the lock at a given time. The rule is that in order to execute a synchronized method in an object, a thread must obtain that object's lock. If the lock is already held by another thread, then the second thread must wait until the first thread releases the lock. If all access to a shared resource takes place inside methods that are synchronized on a given object, then each thread that accesses the resource has exclusive access.
When a resource is only used in part of a method, it's not necessary to make the entire method synchronized. A single statement can be synchronized. The synchronized statement takes the form
synchronized( object ) { statements }(The synchronized statement, for some unknown reason, requires the braces { and } even if they contain just a single statement.) To execute the statements, a thread must first obtain the lock belonging to the specified object. Often, you will say "synchronized(this)" to synchronize on the same object that contains the method. However, it is also possible to synchronize on another object's lock.
In the sample ColoredHelloWorld2 applet, synchronization is important for keeping the applet in a consistent state. For example, the variable isRed is supposed to be true whenever the current display color is red. Consider the following pair of statements from the synchronized method, changeColor():
canvas.setTextColor(Color.red); isRed = true;Just after the second statement is executed, the value of isRed is true. Can we be sure that the displayed color really is red? The answer is yes -- but only because all the color-changing code is in synchronized methods! Otherwise, it would have been possible for some other thread to butt in and change the display color to something else just after the first statement is executed. In that case, the value of isRed would be inconsistent with the actual color. Later, when decisions are made based on the value of isRed, those decisions could be wrong. It's not a big deal here, but there are cases where something like this -- even if it happens very, very rarely -- could be a really big deal.
The complete source code for this example is in the file BlinkingHelloWorld2.java.
In the rest of this section, I will try to explain the most advanced aspect of synchronization, the wait() and notify() methods. These methods are defined in the Object class, and so they can be used with any object. In order to legally call an object's wait() or notify() method, a thread must hold that object's lock. So wait() and notify() are usually called only in synchronized methods and statements.
A thread calls an object's wait() method when it wants to wait for some event to occur. (While it is waiting, it releases its hold on the lock, so that other threads can run.) Some other thread must call the same object's notify() method when the event occurs. When notify() is called, any threads that are waiting will wake up and can continue. Obviously, this requires close coordination between several threads, which means very careful programming.
If notify() is never called, it's possible that a waiting thread might wait forever. You can avoid this with good programming, but you can also put a time limit on how long the thread will wait. This is done by passing a parameter to the wait() method specifying the maximum number of milliseconds that the thread will wait. If notify() has not been called by the end of that time period, the thread will wake up anyway.
As with an earlier example on this page, use of the wait() method requires a try...catch statement. Here are two methods that can be called to wait indefinitely or for a specified time period:
synchronized void waitDelay(int milliseconds) { // Pause for the specified number of milliseconds // OR until the notify() method is called by some other thread. try { wait(milliseconds); } catch (InterruptedException e) { } } synchronized void waitDelay() { // Pause until the notify() method is called by some other thread. try { wait(); } catch (InterruptedException e) { } }The first of these can be used as a kind of interruptable delay. A thread that calls it will pause for the specified number of milliseconds, unless a call to notify() occurs in the meantime. I use this waitDelay() method in the run() method of the BlinkingHelloWorld2 applet. Whenever I set the status variable in that applet to TERMINATE, I call notify(). If the thread is in the middle of a waitDelay(), this will wake it up. Suppose you click on the "Stop!" button just after the runner thread calls waitDelay(300). Because of the call to notify(), the runner thread wakes up and terminates immediately, instead of continuing to sleep for 300 milliseconds before terminating. This avoids a noticeable delay between the time you click on the button and the time that its name changes back to "Blink!". However, this is still a pretty trivial use of wait() and notify(). The final example in this section shows how to use them in a non-trivial way. Here's the last "Hello World" you'll see in these notes:
The complete source code for this applet can be found in the file ScrollingHelloWorld.java. I'll only look at a few aspects of it here.
The thread that animates this applet runs continually as long as this page is visible. The thread is created when the applet is first started, and it is stopped when the applet is destroyed. However, during periods when the applet is stopped, the thread is not actively running. It is just waiting to be notified when the applet is restarted. This behavior is programmed using the applet's wait() and notify() methods.
The runner thread in this applet has three possible statuses: GO to tell it to run the animation, TERMINATE to tell it to terminate because the applet is about to be destroyed, and SUSPEND to tell it to wait for the applet to be restarted. The applet's start() method sets the status to GO. If the thread does not yet exist, it is created and started. Otherwise, the thread is notified that the value of status has changed:
synchronized public void start() { // Called when the applet is being started or restarted. // Create a new thread or restart the existing thread. status = GO; if (runner == null || ! runner.isAlive()) { // Thread doesn't yet exist or has died for some reason. runner = new Thread(this); runner.start(); } else { // Another thread exists and is still alive, but presumably sleeping. notify(); // Wake up the existing thread. } }The stop() and destroy methods merely change the value of status and notify the thread that the value has changed. Note that all these methods must be synchronized, or it would be illegal to call the notify() method:
synchronized public void stop() { // Called when the applet is about to be stopped. // Suspend the thread. status = SUSPEND; notify(); } synchronized public void destroy() { // Called when the applet is about to be permanently destroyed; // Stop the thread. status = TERMINATE; notify(); }The run() method for this example uses the following while loop to run the animation:
while (status != TERMINATE) { synchronized(this) { while (status == SUSPEND) waitDelay(); } if (status == GO) nextFrame(); if (status == GO) waitDelay(250); }This loop is repeated until status becomes equal to TERMINATE. The synchronized statement in this loop,
synchronized(this) { while (status == SUSPEND) waitDelay(); }causes the thread to pause as long as the status is SUSPEND. It's good practice here to use a while statement rather than an if statement, since in general notify() could be called for several different reasons. Just because notify() has been called, it doesn't necessarily mean that the value of status has changed from SUSPEND to something else.
You might wonder why this is synchronized. If it were not synchronized, the following sequence of events would be possible: (1) The runner thread finds that the value of status is SUSPEND and decides to call waitDelay(); (2) some other thread butts in, changes the value of status and calls notify(); (3) the runner thread calls waitDelay() after notify() has already been called. Then the waitDelay() will cause the thread to wait for a notify() that has already occurred and is not going to occur again. This would be a minor disaster: The animation will not properly restart, or the thread will not properly terminate.
[ Next Section | Previous Section | Chapter Index | Main Index ]