HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Sometimes Java applets continue their execution even after the page that contains them is no longer visible. Run a few of them and your computer will slow down dramatically. If you continue you might need to reboot the system to avoid a crash. So you disconnect, reboot, reconnect and start all over again. Isn't it simpler to just disable the applets?

Yes. But you'll lose something if you do. Java isn't only for animations and cute navigation tools (though this article contains a lot about animation). Today everything moves on the Internet: businesses, services, entertaining and more. Suppose you need information today. If not today, then no later than tomorrow. You have three options:

  1. Use the phone and speak with an operator, who will use a Java/native application running on a thin/fat client to query a database. A slow human intermediary will get the information, which will be communicated orally. You might have to note it on a piece of paper...
  2. Connect directly to the company's Web site. Fill out a form and send the data to a servlet/CGI script that will return a page generated dynamically. If you don't get the requested information, you'll have to fill out the form again and repeat the procedure...
  3. Or use a smart applet that guides you like a wizard and helps you get the information. You'll have a dynamic user interface, context-dependent help, friendly messages, tooltips, "Back" and "Next" buttons, validation of the data before it's sent and -- more important -- interactivity specific to the application.
You don't want to miss the opportunity to use that third solution because it's the fastest way to get the information you need. Okay, so don't disable the applets, but what do you do? As a user you can install the latest version of your favorite browser or you can double the RAM memory and buy a faster microprocessor. As a developer you can write friendly applets that can be suspended and resumed anytime. Read this article to find out how.

Thread Persistence
In my previous article ("Persistent Threads for Friendly Applications," JDJ Vol. 3, Issue 10) I defined thread persistence and showed why and how it can be used in stand-alone, computer-intensive applications.

These applications become friendlier because the user can interrupt them and resume the calculation after an undefined period of time. The state of the threads is saved on disk using serialization API. Thread persistence can be implemented easily for a single independent thread, but might become complex if you have to deal with many concurrent threads.

There's a big difference between applets and stand-alone applications, however. What does thread persistence for the applets that run in a browser mean? There are two answers...

  1. It conserves the state of the threads, between stop and restart, after the applets are downloaded and initialized.
  2. It's an essential element if you want to download applets already initialized and customized.
The Java Tutorial (Ref. 1) gives you a number of tips to make the applets more friendly. One is to let users interrupt an applet that executes an animation or something that might be disturbing (e.g., playing sounds). There are a few ways to do that. An old solution is to call the suspend() and resume() methods of the thread that performs the animation. But these methods were deprecated in Java 1.2 because they are deadlock prone. A better alternative is to call the stop() and start() methods of the applet (NOT of the thread). Assuming that you want to interrupt the animation thread in the applet's stop() method, the question is how to assure the persistence of the state of the thread between stop() and start(). The users should be able to resume the animation at the point of interruption.

I have analyzed four ways to make the applets more friendly. The first three don't interrupt the thread, but suspend or commute it in a wait state. The fourth solution interrupts the thread after the stop() method of the applet is called. If the start() method of the applet is called again, the thread is restarted. Conserving the state of the thread in the member variables of the applet guarantees continuity of the animation. The fourth approach thus implements some sort of persistence for the thread that performs the animation, between stop() and (re)start(), after the applet has been downloaded and initialized.

There are two advantages. First, the interruption of the thread frees resources. Note that once the animation is stopped, the users are unlikely to want to resume it. The second advantage consists of the ways to use a second kind of persistence that allows the deployer to customize and then serialize the applet without writing a line of code. A section later in this article ("The Ultimate Use of Thread Persistence") offers details.

BaseFriendlyApplet
The BaseFriendlyApplet class (Listing 1) contains the common code of the next four applets that extend it. I've tried to simulate as simply as possible an applet that performs animation. As the BaseFriendlyApplet class doesn't override the start() and stop() methods inherited from java.applet.Applet, I declared it abstract so that it can't be used in Web pages.

The init() method builds the user interface, whose components are a label and two buttons: "Suspend" and "Resume." The label is the equivalent of a canvas or lightweight component of an animation applet, i.e., it shows the current "frame." (I won't talk about double buffering and sounds; these facilities usually need synchronization, making the thread model more complex.)

The nextFrame() method simulates "the computing of a frame" (it increments the subcounter variable by BIG_NUMBER times). It increments the counter variable and then it calls the setText() method of the label to show "the next frame of the animation." A true animation applet would call its repaint() method instead of setText(). This would generate a PaintEvent, and AWT would call the applet's paint() method. (If your Java interpreter doesn't have a fast JIT compiler, you should delete a "0" from the value of BIG_NUMBER.)

The actionPerformed() method is executed within the AWT drawing and event- handling thread. It's called each time the user clicks one of the applet's buttons. If the user presses "Suspend," actionPerformed() calls the stop() method of the applet. If the user presses "Resume," actionPerformed() invokes the start() method.

Usually, the main methods of an applet -- init(), start(), stop() and destroy() - don't have to be synchronized. Typically, the browser creates a single thread from which it calls these methods in the right order. The subclasses of BaseFriendlyApplet, which override some of these methods, will have to take into account that the start() and stop() methods may be called from two different threads: the one created by the browser for the applet, and the other created for the AWT thread. In addition, the destroy() method may be called, theoretically, while start() or stop() is running within the AWT thread. These methods have to be synchronized so they aren't executed simultaneously within two different threads.

To make testing easier, the "Suspend" and "Resume" buttons remain enabled. A user can click the "Suspend" button twice and the stop() method will be completed twice without an intermediary call of start(). The four subclasses of BaseFriendlyApplet (see below) will have to deal with that.

Using suspend() and resume()
What do you think of a friendly applet that's deadlock prone? This is the case of BadFriendlyApplet (Listing 2), which calls the suspend() method of the java.lang.Thread class.

The applet's start() method creates a thread when it's called for the first time (let's name it the animation thread). The next calls resume this thread in case it was suspended by the applet's stop() method. The threadSuspended flag is used to alternate the suspend() and resume() calls. The destroy() method stops the thread. The animation loop is executed within the run() method.

The animation thread may be suspended during the execution of nextFrame() after the user clicks the "Suspend" button. For a real-world applet this means that the animation can be suspended during the computation of the next frame. If the browser asked the applet to repaint itself, that would be a problem even if the applet uses double buffering.

BadFriendlyApplet seems to be stable, but minor changes can lead to disaster. When I added a Thread.sleep(10) call right after thread.suspend(), AppletViewer "performed an illegal operation" (this is an error message from Windows) right before closing. This didn't happen with other browsers. When I made BIG_NUMBER == 0 and clicked the "Suspend" and "Resume" buttons as fast and as often as I could, the applet froze in Microsoft Internet Explorer. Netscape Navigator isn't perfect either. When I modified the applet to allow consecutive suspend() or resume() calls (with BIG_NUMBER == 0), I had to press "Resume" x times after x clicks on "Suspend" to resume the thread. The methods suspend() and resume() were called from the same thread (the AWT thread), so theoretically the problems shouldn't have appeared.

It isn't worth wasting your time trying to freeze the browsers. You just have to avoid the use of suspend() and resume(). The stop() method of the Thread class mustn't be used either, because it's unsafe. Java 1.2 has deprecated all three of these methods. For details see "Why JavaSoft Is Deprecating Thread.stop, Thread.suspend and Thread.resume" (Ref. 2).

Using wait() and notifyAll()
Is there a solution to writing applets that are both friendly and safe? Of course. In fact, there are many. One of them is based on the use of the wait() and notifyAll() methods of the java.lang.Object class. This section and the next discusses locks and wait sets. The Java Language Specification (Ref. 3) dedicates an entire chapter to these subjects ("17 Threads and Locks").

The threadSuspended flag has a new role in the case of GoodFriendlyApplet (Listing 3). When the user clicks the "Suspend" button, this flag will be set to true in the applet's stop() method, which is called from actionPerformed() (inherited from BaseFriendlyApplet). The stop() method will run in this case within the AWT thread. After threadSuspended is set to true, the applet's run() method, which is executed within a different thread (the animation thread), will perform a loop that calls wait() as long as threadSuspended is true.

The wait() method is invoked from a synchronized block. This means that the animation thread has already acquired (or locked) the lock of the GoodFriendlyApplet instance. (Every object has an associated lock that is used for synchronization purposes and that must be locked before the calls of wait(), notify() and notifyAll().)

The wait() call adds the current thread (i.e. the animation thread) to the wait set of the applet, disables the current thread for thread scheduling purposes and releases (or unlocks) the lock. Then it waits for a notification from another thread (i.e., the AWT thread or the browser's thread). (Every object has an associated wait set that represents the list of the threads that have called the object's wait() method and that have not yet been notified.)

When the user presses the "Resume" button, actionPerformed() will call the applet's start() method. This method, which is declared synchronized, will set threadSuspended to false and call notifyAll(). The notifyAll() method will remove the animation thread from the wait set and reenable it for thread scheduling.

After notification, the wait() method can't return the control immediately. Before return, it must reacquire the applet's lock and it can't do that before the synchronized start() method is completed.

An InterruptedException is thrown by any of the Thread.sleep() or wait() calls after the applet's destroy() method invokes the interrupt() method of the animation thread. The exception is caught and the break instruction will transfer the control outside the animation loop. The animation thread will die.

The animation is suspended when the wait() method is called, and resumed after wait() returns the control. The animation can't be interrupted during the execution of nextFrame() unless the browser is closed while nextFrame() computes the next frame, but this isn't an issue.

The pattern of GoodFriendlyApplet may work fine for a simple applet. Some problems might appear in the case of a complex applet from the real world. The next section identifies these problems and suggests a solution.

Using wait() and notify()
What would happen if you used notify() instead of notifyAll()? The user won't see any differences in the case of GoodFriendlyApplet. But if another applet has two or more threads that call the applet's wait() method, the behavior of the applet may depend on the JVM implementation. This is because notify() chooses a single thread from wait set to be awakened, and, as The Java Language Specification (Ref. 4) states, "The choice is arbitrary and at the discretion of the implementation." The chosen thread may not be the wanted one. Unlike notify(), the notifyAll() method wakes all the threads from the wait set. Those threads that shouldn't be awakened must call wait() again. This is why GoodFriendlyApplet invokes wait() within a loop. The threadSuspended flag indicates whether the notification has come from where it was expected.

If I declare run() synchronized, the AWT thread will freeze when the user presses "Suspend" or "Resume." The explanation for that is simple. If run() is synchronized, it will keep the applet's lock during its entire execution. When actionPerformed() calls start() or stop(), the AWT thread tries to acquire the applet's lock, which is kept by the animation thread. The user's clicks on the applet's buttons won't have any more effect, and blocking the AWT thread might easily freeze the browser. The worst thing that can happen is the system will crash. Nobody will run the applet a second time. You have to be careful at synchronization not to transform a friendly applet into a hostile one.

The above problems are the consequences of programming errors. The browser can do nothing against deadlocks. Can you avoid mistakes? Yes. You can use different locks to correctly pair the wait() and notify() calls. BetterFriendlyApplet (Listing 4) shows how. Instead of its own lock, the new applet uses the lock of an object referred by the LOCK member variable. The code for the start(), stop() and destroy() methods is enclosed in synchronized blocks. The applet uses LOCK.wait() and LOCK.notify() instead of its own wait() and notifyAll() methods (inherited from java.lang.Object). It is then no longer necessary to include wait() within a loop. However, the threadSuspended flag is checked twice in the run() method. The first check avoids the acquisition and release of the lock when these operations aren't necessary (the wait() method doesn't have to be called). The second check of the flag is essential because its value may change right after the first verification, before entering the synchronized block.

Using the LOCK variable minimizes the influence of the code that suspends and resumes the animation on the code that implements the logic of the applet (i.e., the animation). A complex applet may now use the synchronized keyword without coming into conflict with the general mechanism that makes the applet friendlier. For example, the run() method may now become synchronized without blocking the AWT thread. A well-designed applet should call wait() after repaint() (within the animation thread), and await a notification from paint(), which runs within the AWT thread). This way, no frame will be lost and no half-computed frame will be shown.

In both cases of GoodFriendlyApplet and BetterFriendlyApplet, the notifyAll() and notify() methods may be invoked before the wait() from run() call if the user clicks "Resume" immediately after "Suspend." Nothing bad happens here because the wait set doesn't contain any threads, and the wait() method isn't called again (threadSuspended is set to false right before notification). In addition, BetterFriendlyApplet could have used LOCK.notifyAll() instead of LOCK.notify(), and the result would have been the same. Finally, the private keyword that precedes the declaration of LOCK doesn't prevent the incorrect use of LOCK's wait(), notify() and notifyAll() methods in regard to purposes other than the implementation of the mechanism that suspends and resumes the animation.

All three of the above solutions (Bad, Good and Better) keep the animation thread alive between init() and destroy(). The next applet shows how to kill this thread between stop() and (re)start(), and demonstrates the advantages.

Persistence between stop() and (re)start()
BestFriendlyApplet (Listing 5) is the applet that implements the persistence of the animation thread. When the user clicks the "Suspend" button, the applet's stop() method calls the interrupt() method of the animation thread. The Thread.sleep() call from run() will throw an InterruptedException. The break instruction will transfer the control outside the animation loop, and the run() method will complete its execution. This means that the animation thread will die, but its state will be kept in the counter and subcounter member variables, inherited from BaseFriendlyApplet. When the user clicks the "Resume" button, the applet's start() method will re-create and restart the thread, and the animation will be resumed from the point at which it was suspended.

The user can also press the "Resume" button right after "Suspend," before the death of the thread. If the animation thread is still alive, the start() method must not create another thread. The correct solution is to cancel the interruption of the thread with the help of the intrCanceled flag. The InterruptedException will still be thrown, but the animation loop isn't broken anymore. The cancellation is canceled when the user clicks "Suspend," "Resume," "Suspend"...

The animation thread controls the moment when it is killed, so that the nextFrame() method completes its execution before the animation is suspended.

Before discussing why this solution is the best, I'll explain why the initialization of the LOCK member variable had to be changed. If I had declared and initialized LOCK as I've done in BetterFriendlyApplet, the applet's serialization would have failed because java.lang.Object isn't serializable. If I had declared LOCK transient, the object referred by this variable would have been ignored at serialization, and it wouldn't have been re-created at deserialization (instance initializers aren't executed at deserialization). Hence, the start() method would have thrown a NullPointerException. If I had made LOCK class variable (by declaring it static), I would have been mistaken because this variable would have been shared among all of the BestFriendlyApplet instances that would have run at a given moment within the same browser. Probably nothing bad would have happened in the case of this applet, but annoying effects would have appeared if LOCK.wait() and LOCK.notify() had been called. (The thread of an applet could have notified the thread of another applet.) The right solution is to assign a reference of a serializable object to the LOCK variable. The simplest way to do this is with the use of anonymous classes.

private final Object LOCK
= new java.io.Serializable() {};

Note that the locks and the wait sets of the objects referred by the member variables aren't serialized because the fields of java.lang.Object don't refer them. The locks and the wait sets are internal data structures managed by the Java Virtual Machine. The programmer can't access them, but you must be aware of their existence to understand thread synchronization and the behavior of the wait(), notify() and notifyAll() methods.

The pattern of BestFriendlyApplet offers two important advantages. First, the thread created in the start() method is interrupted after the call of the stop() method. Hence, the resources allocated to the animation thread are released. Again, it is unlikely that the users will resume the animation after they stop it. (But they can resume it, if they want.) The second advantage is that the applet is serializable after the stop() method is called. The other applets (Bad, Good and Better) aren't serializable because the java.lang.Thread class isn't. The following section gives more details about the second advantage.

The Ultimate Use of Thread Persistence
Java 1.1 has extended the <APPLET> tag, so that instead of the CODE attribute, you can use the OBJECT attribute, whose value must be the name of a file that contains a serialized representation of an applet. After it downloads this file, the browser will deserialize the applet and call its start() method. (The init() method isn't invoked.) This allows the deployer to offer many customized versions of the same applet without writing a line of code. Such a deployment might be useful for the complex applets, whose customizations need something more than the parameters of the <APPLET> tag. You have to be aware that Netscape Navigator and Microsoft Internet Explorer don't yet recognize the OBJECT attribute. Hence, to use this feature of Java 1.1, you will have to use Sun HotJava or AppletViewer. (You may use any Java 1.1-compatible browser, including Explorer and Navigator, to run the applets presented in this article as long as you only use the attributes of the <APPLET> tag from Java 1.0.)

How does the OBJECT attribute work? An example is the best answer for this question.

  1. First, you must create an .HTML file (e.g., CodeAttrib.html) that uses the <applet> tag      with the CODE attribute.

    <applet code = "BestFriendlyApplet.class"
    width=200 height=200> </applet>

  2. Next, run "AppletViewer (e.g., CodeAt trib.html.)
  3. Let the applet run a while, then select the "Stop" item from the Applet menu of AppletViewer. AppletViewer will then call the applet's stop() method. You'll have to wait until the animation thread is interrupted.
  4. Select the "Save..." item from the Applet menu. AppletViewer will show a dialog box, whose title is "Serialize Applet Into File" (see Figure 1).

    Figure 1
    Figure 1:

  5. Type, for example, "aBestFriendlyApplet.ser" (without quotes) within the "File name" text field, and click the "Save" button. The dialog box will be closed, and AppletViewer will serialize the applet in aBestFriendlyApplet.ser file.
  6. Close AppletViewer.
  7. Create a second .HTML file (e.g.,
    ObjectAttrib.html) that uses the <APPLET> tag with the OBJECT attribute.
    <applet object = "aBestFriendlyApplet.ser"
    width=200 height=200> </applet>
  8. Finally, run "AppletViewer ObjectAttrib.html" or download ObjectAttrib.html in the HotJava browser. AppletViewer/HotJava will deserialize the applet and call its start() method. (The init() method isn't invoked.) The animation will be resumed.
One small problem is that if you select the "Save..." item and click the "Save" button (steps 4 and 5) immediately after "Stop" (step 3), the animation thread may still be alive. If so, its associated Thread object is referred by the thread member variable of the applet. AppletViewer will print an error message to the console because the java.lang.Thread class doesn't implement java.io.Serializable.

in appletSave:
java.io.NotSerializableException:
java.lang.Thread

Wait a little and then try again to serialize the applet, using the "Save..." item. (Repeat steps 4 and 5.) This inconvenience is minor because the deployers serialize the applet before they insert it into public Web pages. It's incorrect to declare the thread variable transient to avoid the error message because this would allow the serialization of the applet during the computing of the next frame. Remember, the applet encapsulates the state of the animation thread. You have to let this thread arrange its own death and store null in the thread member variable.

This is an interesting trick. The applet can't be serialized before the execution of "thread = null;" because the Thread class isn't serializable. Nevertheless, the applet can be serialized after the thread variable is set to null (in the run() method, before break) because the Serialization API doesn't look at the type of the member variables, but at their values.

The applet's stop() method can't wait for the death of the animation thread. If it did this, the user wouldn't be able to cancel the suspension of the animation because the AWT thread and thus the "Resume" button would be blocked during the wait period of stop().

Applet Persistence in the Real World
Imagine the following scenario. You are developing an applet that implements a neural network, and you want to show visitors to your site how the results have improved during training. From time to time you stop the applet and serialize it. Then you restart the applet and training continues. After a while, the network reaches its optimum, and then will begin to forget what it has learned. What you have to do is insert the serialized variants of the applet that were saved before and right after the optimum was reached into a Web page. This technique can be used for any kind of applet that shows the evolution in time of a phenomenon, and you won't have to write I/O code or develop multiple versions of the applet.

The above example is a specialized one. The pattern of BestFriendlyApplet is very general and has a double role: to make the applets both friendly and serializable. The latter might not interest you right now, however, because Navigator and Internet Explorer don't yet recognize the OBJECT attribute of the <APPLET> tag. There is one more disadvantage: the .ser files increase the download time. You can use the transient keyword to control what is serialized, but the objects referred by the member variables that your applet inherits from java.applet.Applet will be serialized. Among these objects are the AWT components of the applet.

In addition to simplifying customization, there is one more advantage: the init() method isn't called anymore, so a fast initialization comes after the slow download.

You are the one who decides what's best for your applets. Even if you don't use serialization, you can still make the applets more friendly without performance costs and without limiting the number of target browsers.

Summary
This article has answered many questions: What could applet persistence mean for the real world? How can we implement persistence for threads and applets? How do we write thread-safe friendly applets? How can we pair the wait() and notify() calls? What's the difference between notify() and notifyAll()? Why not use suspend() and resume()? But this article is not just a list of questions and answers. Starting with a nonserializable deadlock-prone applet, I've identified the problems, found the solutions and designed a pattern for serializable friendly applets.

In my next article I'll discuss the persistence of the Swing components.

References
1. Mary Campione and Kathy Walrath, The Java Tutorial, Addison Wesley.
java.sun.com/docs/books/tutorial/
2. Sun Microsystems, "Why JavaSoft Is Deprecating Thread.stop, Thread.suspend and Thread.resume."
java.sun.com/products/jdk/1.2/docs/guide/misc/threadPrimitiveDeprecation.html
3. James Gosling, Bill Joy and Guy Steele, The Java Language Specification, Addison Wesley.
java.sun.com/docs/books/jls/

About the Author
Andrei Cioroianu, an independent Java developer, has a BS in mathematics/computer science and an MS in artificial intelligence. His focus is on 3D graphics (Java 3D), software components (JavaBeans) and user interface (AWT, JFC). You can reach Andrei at [email protected]

	

Listing 1: BaseFriendlyApplet.
 
import java.awt.*; 
import java.awt.event.*; 

public abstract class BaseFriendlyApplet 
    extends java.applet.Applet 
    implements ActionListener { 

    static final int MAX_COUNTER= Integer.MAX_VALUE; 
    static final int BIG_NUMBER = 100000000; 
    static final int MAX_SUBCOUNTER = BIG_NUMBER*10; 
    int counter = 0; 
    int subcounter = 0; 
    Label label; 

    public void init() { 
        setLayout(new BorderLayout()); 
        label = new Label(“0”, Label.CENTER); 
        add(label, BorderLayout.CENTER); 
        Panel panel = new Panel(); 
        panel.setLayout(new GridLayout(1, 2)); 
        Button button_s = new Button(“Suspend”); 
        button_s.addActionListener(this); 
        panel.add(button_s); 
        Button button_r = new Button(“Resume”); 
        button_r.addActionListener(this); 
        panel.add(button_r); 
        add(panel, BorderLayout.SOUTH); 
    } 

    void nextFrame() { 
        for (int i = 0; i < BIG_NUMBER; i++) 
            if (subcounter < MAX_SUBCOUNTER) 
                subcounter++; else subcounter = 1; 
        if (counter < MAX_COUNTER) 
            counter++; else counter = 0; 
        label.setText(Integer.toString(counter)); 
    } 

    public void actionPerformed(ActionEvent e) { 
        String what = e.getActionCommand(); 
        if (what.equals(“Suspend”)) stop(); 
        if (what.equals(“Resume”)) start(); 
    } 

} 
  

Listing 2: BadFriendlyApplet.
 
public class BadFriendlyApplet 
    extends BaseFriendlyApplet 
    implements Runnable { 

    Thread thread = null; 
    boolean threadSuspended = false; 

    public synchronized void start() { 
        if (thread == null) { 
            thread = new Thread(this); 
            thread.start(); 
        } else 
            if (threadSuspended) { 
                threadSuspended = false; 
                thread.resume(); 
            } 
    } 
  
    public synchronized void stop() { 
        if (thread != null && !threadSuspended) { 
            threadSuspended = true; 
            thread.suspend(); 
        } 
    } 

    public synchronized void destroy() { 
        thread.stop(); 
        thread = null; 
    } 

    public void run() { 
        while (true) { 
            nextFrame(); 
            try { 
                Thread.sleep(10); 
            } catch (InterruptedException e) { 
            } 
        } 
    } 

} 

Listing 3: GoodFriendlyApplet.
 
public class GoodFriendlyApplet 
    extends BaseFriendlyApplet 
    implements Runnable { 

    Thread thread = null; 
    boolean threadSuspended = false; 

    public synchronized void start() { 
        if (thread == null) { 
            thread = new Thread(this); 
            thread.start(); 
        } else 
            if (threadSuspended) { 
                threadSuspended = false; 
                notifyAll(); 
            } 
    } 
  
    public synchronized void stop() { 
        if (thread != null && !threadSuspended) 
            threadSuspended = true; 
    } 

    public synchronized void destroy() { 
        thread.interrupt(); 
        thread = null; 
    } 

    public void run() { 
        while (true) { 
            nextFrame(); 
            try { 
                Thread.sleep(10); 
                if (threadSuspended) 
                    synchronized (this) { 
                        while (threadSuspended) 
                            wait(); 
                    } 
            } catch (InterruptedException e) { 
                break; 
            } 
        } 
    } 

} 

Listing 4: BetterFriendlyApplet.
 
public class BetterFriendlyApplet 
    extends BaseFriendlyApplet 
    implements Runnable { 

    Thread thread = null; 
    boolean threadSuspended = false; 
    private final Object LOCK = new Object(); 

    public void start() { 
        synchronized (LOCK) { 
            if (thread == null) { 
                thread = new Thread(this); 
                thread.start(); 
            } else 
                if (threadSuspended) { 
                    threadSuspended = false; 
                    LOCK.notify(); 
                } 
        } 
    } 

    public void stop() { 
        synchronized (LOCK) { 
            if (thread != null && !threadSuspended) 
                threadSuspended = true; 
        } 
    } 

    public void destroy() { 
        synchronized (LOCK) { 
            thread.interrupt(); 
            thread = null; 
        } 
    } 

    public void run() { 
        while (true) { 
            nextFrame(); 
            try { 
                Thread.sleep(10); 
                if (threadSuspended) 
                    synchronized (LOCK) { 
                        if (threadSuspended) 
                            LOCK.wait(); 
                    } 
            } 
            catch (InterruptedException e) { 
                break; 
            } 
        } 
    } 

} 

Listing 5: BestFriendlyApplet.
 
public class BestFriendlyApplet 
    extends BaseFriendlyApplet 
    implements Runnable { 

    Thread thread = null; 
    boolean intrCanceled = false; 
    private final Object LOCK 
        = new java.io.Serializable() {}; 

    public void start() { 
        synchronized (LOCK) { 
            if (thread == null) { 
                thread = new Thread(this); 
                thread.start(); 
            } else 
                if (thread.isInterrupted() 
                    && !intrCanceled) 
                    intrCanceled = true; 
        } 
    } 
  
    public void stop() { 
        synchronized (LOCK) { 
            if (thread != null) 
                if (!thread.isInterrupted()) { 
                    intrCanceled = false; 
                    thread.interrupt(); 
                } else 
                    if (intrCanceled) 
                        intrCanceled = false; 
        } 
    } 

    public void run() { 
        while (true) { 
            nextFrame(); 
            try { 
                Thread.sleep(10); 
            } catch (InterruptedException e) { 
                synchronized (LOCK) { 
                    if (!intrCanceled) { 
                        thread = null; 
                        break; 
                    } else 
                        intrCanceled = false; 
                } 
            } 
        } 
    } 

} 
  

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.