Chapter 11. Self-Running Objects

IN THIS CHAPTER

In an object-based application, most objects are passive. A passive object just sits there waiting for one of its methods to be invoked. A passive object's private member variables can only be changed by the code in its own methods, so its state remains constant until one of its methods is invoked. In a multithreaded environment like Java, threads can run within objects to make the objects active. Objects that are active make autonomous changes to themselves.

Sometimes in modeling a system, it becomes apparent that if some of the objects were active, the model would be simplified. Earlier in this book, classes that implemented Runnable were instantiated, passed to one of the constructors of Thread, and then start() was invoked. This style required a user of a class to know that a thread needed to be started to run within it, creating a burden on the user of the class. In addition, because the user of the class created the Thread object for it, a reference to Thread was available for misuse. The user of the class could erroneously set the priority of the thread, suspend it at a bad time, or outright stop the thread when the object it was running in was in an inconsistent state. Having to activate objects externally is both inconvenient and potentially hazardous. In this chapter, I'll show you how to have an active object transparently create and start up its own internal thread.

Simple Self-Running Class

The class SelfRun, shown in Listing 11.1, demonstrates a simple example of an active object. During construction, it automatically starts an internal thread running.

Example 11.1. SelfRun.java—A Simple Self-Running Class

 1: public class SelfRun extends Object implements Runnable {
 2:     private Thread internalThread;
 3:     private volatile boolean noStopRequested;
 4:
 5:     public SelfRun() {
 6:         // other constructor stuff should appear here first ...
 7:         System.out.println("in constructor - initializing...");
 8:
 9:         // Just before returning, the thread should be
10:         // created and started.
11:         noStopRequested = true;
12:         internalThread = new Thread(this);
13:         internalThread.start();
14:     }
15:
16:     public void run() {
17:         // Check that no one has erroneously invoked
18:         // this public method.
19:         if ( Thread.currentThread() != internalThread ) {
20:             throw new RuntimeException("only the internal " +
21:                 "thread is allowed to invoke run()");
22:         }
23:
24:         while ( noStopRequested ) {
25:             System.out.println("in run() - still going...");
26:
27:             try {
28:                 Thread.sleep(700);
29:             } catch ( InterruptedException x ) {
30:                 // Any caught interrupts should be habitually
31:                 // reasserted for any blocking statements
32:                 // which follow.
33:                 Thread.currentThread().interrupt();
34:             }
35:         }
36:     }
37:
38:     public void stopRequest() {
39:         noStopRequested = false;
40:         internalThread.interrupt();
41:     }
42:
43:     public boolean isAlive() {
44:         return internalThread.isAlive();
45:     }
46: }

SelfRun implements Runnable (line 1), as did the earlier examples. The two member variables are used to maintain and control the internal thread. The private variable internalThread (line 2) holds a reference to the thread that is used to run the object. The private variable noStopRequested (line 3) is used as a flag to indicate whether or not the internal thread should continue processing. The internal thread continues to process code as long as noStopRequested is true. It is marked volatile because two different threads access it: the internal thread reads it, and an external thread will change it.

In the constructor (lines 5-14), after the class-specific initialization activities have all been completed (lines 6-7), the noStopRequested flag is initially set to true (line 11). A new Thread object is constructed by passing in this, the reference to the SelfRun object being created. This thread is used as the internal thread for this object and is automatically started (line 13). By the time the constructor returns, an internal thread has been created and has been started. We don't know if run() has been called yet by this internal thread, but the correctness of the design should not depend on its being called before the constructor returns. You should keep in mind that run() might be invoked before the constructor returns, right after it returns, or some time after it returns. The timing is dependent on the whims of the thread scheduler.

In run() (lines 16–36), the very first thing done is a check that run() was only invoked by the internal thread (lines 19–22). This is necessary to ensure that no one mistakenly invokes run() from outside this class, something that is completely possible because run() is public. If any other thread invokes run(), a RuntimeException is thrown to indicate the error (lines 20–21).

The while loop within run() (lines 24–35) continues until noStopRequested becomes false. In this example, a simple message is printed each time through just to show the internal activities (line 25). The internal thread then sleeps briefly before looping again (line 28). If this sleep() is interrupted, its InterruptedException is caught right away (line 29). After catching the exception, the internal thread reinterrupts itself (line 33) in case any other interrupt-detecting statements (such as wait(), or another sleep()) are present before the orderly shutdown can complete. This is a good habit to get into when interrupting a thread is used as a signaling mechanism for stopping.

To request that the internal thread gracefully die as soon as possible, the stopRequest() method is used (lines 38–41). When invoked, it sets noStopRequested to false (line 39). stopRequest() then interrupts the internal thread in case it is blocked on an interruptible statement (such as wait() or sleep()) so that it gets a chance to notice that noStopRequested is now false.

Because a stopRequest() is only a request that the thread die as soon as it has completed any cleanup activities, another method is needed to determine if the thread has died yet. The isAlive() method (lines 43–45) is used to proxy the query to the internal thread to determine if it is still alive.

The SelfRunMain class is used to demonstrate the SelfRun class in action. The code for it is in Listing 11.2.

Example 11.2. SelfRunMain.java—Demonstration Code for SelfRun

1: public class SelfRunMain extends Object {
2:     public static void main(String[] args) {
3:         SelfRun sr = new SelfRun();
4:
5:         try { Thread.sleep(3000); } catch ( InterruptedException x ) { }
6:
7:         sr.stopRequest();
8:     }
9: }

SelfRunMain simply constructs a SelfRun (line 3), lets it run for 3 seconds (line 5), and then requests that it stop soon (line 7). The main feature to note is that all I had to do was to construct a SelfRun. The hassle of creating a Thread and starting it is gone. In fact, if I don't have a need to ever stop it, I don't even have to be aware that it has a thread running within it!

When SelfRunMain is run, the following output will be produced (your output should match):

in constructor - initializing...
in run() - still going...
in run() - still going...
in run() - still going...
in run() - still going...
in run() - still going...

Using an Inner Class to Hide run()

In the previous example, steps had to be taken to protect the class from erroneous invocations of run(). Although the class is adequately protected from external intrusion, a user of the class can still fall prey to the lure of calling run(). Or perhaps a user might notice that the class implements Runnable and be tempted to create a Thread and start it. In both cases, a quite unexpected RuntimeException will be thrown.

A better approach available to JDK 1.1 and later developers is to use an inner class to hide the implementation of run() from external code. The class InnerSelfRun, shown in Listing 11.3, demonstrates how this can be done.

Example 11.3. InnerSelfRun.java—Hiding the run() Method in an Inner Class

 1: public class InnerSelfRun extends Object {
 2:     private Thread internalThread;
 3:     private volatile boolean noStopRequested;
 4:
 5:     public InnerSelfRun() {
 6:         // other constructor stuff should appear here first ...
 7:         System.out.println("in constructor - initializing...");
 8:
 9:         // before returning, the thread should be started.
10:         noStopRequested = true;
11:
12:         Runnable r = new Runnable() {
13:                 public void run() {
14:                     try {
15:                         runWork();
16:                     } catch ( Exception x ) {
17:                         // in case ANY exception slips through
18:                         x.printStackTrace();
19:                     }
20:                 }
21:             };
22:
23:         internalThread = new Thread(r);
24:         internalThread.start();
25:     }
26: 
27:     private void runWork() {
28:         while ( noStopRequested ) {
29:             System.out.println("in runWork() - still going...");
30:
31:             try {
32:                 Thread.sleep(700);
33:             } catch ( InterruptedException x ) {
34:                 // Any caught interrupts should be reasserted
35:                 // for any blocking statements which follow.
36:                 Thread.currentThread().interrupt();
37:             }
38:         }
39:     }
40:
41:     public void stopRequest() {
42:         noStopRequested = false;
43:         internalThread.interrupt();
44:     }
45:
46:     public boolean isAlive() {
47:         return internalThread.isAlive();
48:     }
49: }

Most of InnerSelfRun is the same as SelfRun, but there are a few key differences. The biggest change is that InnerSelfRun does not implement Runnable (line 1), but instead uses an anonymous, inner class (lines 12–21) to create a Runnable. The Runnable that is created is passed as a parameter to the constructor of Thread (line 23). In the inner class, run() is implemented (lines 13–20). The private method runWork() is called (line 15) from within a try/catch block. Any exception that slips all the way up through runWork() is caught (line 16) and has its stack trace printed (line 18). Because run() is implemented in an inner class, it is not accessible from the outside, and there is no danger of its being accidentally called. This inner class's run() method can access the private method runWork() of its enclosing class, but no code outside of InnerSelfRun can accidentally call runWork().

There is no longer any need to check which thread is calling runWork() because it cannot be called from outside this class (no checking between lines 27 and 28). The message printed in the while loop is slightly different (line 29) than before. Otherwise, the remainder of the InnerSelfRun class definition is the same as SelfRun.

InnerSelfRunMain (Listing 11.4) simply creates an InnerSelfRun (line 3), lets it run for 3 seconds (line 5), and then requests that it stop soon (line 7). As before, all that I had to do was instantiate an InnerSelfRun. I didn't have, nor did I need to have, any knowledge that it was an active object.

Example 11.4. InnerSelfRunMain.java—Demonstration Code for InnerSelfRun

1: public class InnerSelfRunMain extends Object {
2:     public static void main(String[] args) {
3:         InnerSelfRun sr = new InnerSelfRun();
4:
5:         try { Thread.sleep(3000); } catch ( InterruptedException x ) { }
6:
7:         sr.stopRequest();
8:     }
9: }

When InnerSelfRunMain is run, it produces the following output (your output should be the same):

in constructor - initializing...
in runWork() - still going...
in runWork() - still going...
in runWork() - still going...
in runWork() - still going...
in runWork() - still going...

Additional Functionality to Consider

You can extend the concept of a self-running object to allow more details about the internal thread to be specified:

  • A ThreadGroup for the internal thread can be passed to the class's constructor and then passed to the Thread constructor.

  • A name for the internal thread can be passed to the class's constructor and then passed to the Thread constructor.

  • The methods getInternalThreadName() and setInternalThreadName() can be added if desired. They would simply proxy the request to the underlying thread internalThread.

  • A priority for the internal thread can be passed to the class's constructor and then internalThread.setPriority() can be invoked before the thread is started.

  • The methods getInternalThreadPriority() and setInternalThreadPriority() can be added if desired. They would simply proxy the request to internalThread.

  • If it's inappropriate for the internal thread to be started in the constructor, a new method can be added to allow it to be started later:

    public void start() {
        if ( neverStarted ) {
            neverStarted = false;
            internalThread.start();
        }
    }
    
  • The suspendRequest() and resumeRequest() techniques of Chapter 5, "Gracefully Stopping Threads," can be added if needed.

Example: Animated Images on a JComponent

In this example, I'll show you how to animate a set of images using a customized JComponent that automatically runs a thread inside itself to flip through the images. The images are generated at construction time, but you can modify this design to animate images from any source: computer-generated or from a set of files.

The customized component Squish, shown in Listing 11.5, draws the top half of an ellipse with successively smaller heights. It appears that a mound is being squished down until it is flat. Then the cycle starts over with the full-height, top-half ellipse. The work to animate the images is transparent to the user of the class as the component creates its own internal thread to flip through the images.

Example 11.5. Squish.java—The Animated Image Component

  1: import java.awt.*;
  2: import java.awt.image.*;
  3: import java.awt.geom.*;
  4: import javax.swing.*;
  5:
  6: public class Squish extends JComponent {
  7:     private Image[] frameList;
  8:     private long msPerFrame;
  9:     private volatile int currFrame;
 10:
 11:     private Thread internalThread;
 12:     private volatile boolean noStopRequested;
 13:
 14:     public Squish(
 15:                 int width,
 16:                 int height,
 17:                 long msPerCycle,
 18:                 int framesPerSec,
 19:                 Color fgColor
 20:             ) {
 21:
 22:         setPreferredSize(new Dimension(width, height));
 23:
 24:         int framesPerCycle =
 25:                 (int) ( ( framesPerSec * msPerCycle ) / 1000 );
 26:         msPerFrame = 1000L / framesPerSec;
 27:
 28:         frameList =
 29:             buildImages(width, height, fgColor, framesPerCycle);
 30:         currFrame = 0;
 31: 
 32:         noStopRequested = true;
 33:         Runnable r = new Runnable() {
 34:                 public void run() {
 35:                     try {
 36:                         runWork();
 37:                     } catch ( Exception x ) {
 38:                         // in case ANY exception slips through
 39:                         x.printStackTrace();
 40:                     }
 41:                 }
 42:             };
 43:
 44:         internalThread = new Thread(r);
 45:         internalThread.start();
 46:     }
 47:
 48:     private Image[] buildImages(
 49:                 int width,
 50:                 int height,
 51:                 Color color,
 52:                 int count
 53:             ) {
 54:
 55:         BufferedImage[] im = new BufferedImage[count];
 56:
 57:         for ( int i = 0; i < count; i++ ) {
 58:             im[i] = new BufferedImage(
 59:                     width, height, BufferedImage.TYPE_INT_ARGB);
 60:
 61:             double xShape = 0.0;
 62:             double yShape =
 63:                 ( (double) ( i * height ) ) / (double) count;
 64:
 65:             double wShape = width;
 66:             double hShape = 2.0 * ( height - yShape );
 67:             Ellipse2D shape = new Ellipse2D.Double(
 68:                         xShape, yShape, wShape, hShape);
 69: 
 70:             Graphics2D g2 = im[i].createGraphics();
 71:             g2.setColor(color);
 72:             g2.fill(shape);
 73:             g2.dispose();
 74:         }
 75:
 76:         return im;
 77:     }
 78:
 79:     private void runWork() {
 80:         while ( noStopRequested ) {
 81:             currFrame = ( currFrame + 1 ) % frameList.length;
 82:             repaint();
 83:
 84:             try {
 85:                 Thread.sleep(msPerFrame);
 86:             } catch ( InterruptedException x ) {
 87:                 // reassert interrupt
 88:                 Thread.currentThread().interrupt();
 89:                 // continue on as if sleep completed normally
 90:             }
 91:         }
 92:     }
 93:
 94:     public void stopRequest() {
 95:         noStopRequested = false;
 96:         internalThread.interrupt();
 97:     }
 98:
 99:     public boolean isAlive() {
100:         return internalThread.isAlive();
101:     }
102:
103:     public void paint(Graphics g) {
104:         g.drawImage(frameList[currFrame], 0, 0, this);
105:     }
106: }

Squish extends JComponent so that it can inherit the functionality of a generic Swing component (line 6). Notice that although it will be running an internal thread, it does not implement the Runnable interface (line 6). This example uses the technique of hiding run() in an anonymous, inner class.

The set of images to animate is held in Image array frameList (line 7). The number of milliseconds that each frame should be shown before flipping to the next one is held in msPerFrame (line 8). The current index into the frameList is held in the volatile member variable currFrame (line 9). It is marked as volatile because it is modified by the internal thread inside runWork() (line 81), and read by the event-handling thread inside paint() (line 104). The thread running inside this component is referenced by internalThread (line 11). The flag indicating whether or not a stop has been requested is held in noStopRequested (line 12).

The constructor (lines 14–46) takes several parameters. The component's width in pixels is passed in through width (line 15). Its height is passed in through height (line 16). The number of milliseconds it should take for the mound to be squished down from its full height is specified through msPerCycle (line 17). The number of frames to be flipped though per second is framesPerSec (line 18). The higher the frames per second rate is (up to about 30 fps), the smoother the animation appears, but the greater the demand on the processor. The last parameter fgColor (line 19) is simply the color with which the mound should be drawn.

The preferred size for this component is simply the combination of the width and height passed in (line 22). The number of images to generate for a full cycle from tall to flat is calculated based on the passed-in parameters (lines 24–25). The time that each frame should be shown for is calculated (line 26). The images to use are computer generated and stored in frameList (lines 28–29), and the index into this list is initialized to be 0 (line 30). The rest of the constructor (lines 32–45) implements the standard internal thread pattern I showed you earlier in this chapter.

The buildImages method (lines 48–77) creates all of the frames to be flipped though during animation. A set of BufferedImage objects is created and drawn onto using Graphics2D methods. This set of images is returned to the caller, which in this case is the constructor.

Control of which frame to display is managed inside runWork() (lines 79–92). Each time through the while loop, a check is done to see if a stop has been requested (line 80). If not, the frame number is incremented and wrapped around back to 0 if the last frame has been shown (line 81). After the frame number advances, a repaint request is submitted to the event queue so that the new frame is drawn as soon as possible (line 82). Before looping again, the internal thread sleeps for the interframe interval (line 85). If this sleep is interrupted (probably by stopRequest()), the InterruptedException is caught (line 86). As a matter of good style, the internal thread is reinterrupted (line 88) in case any other interruptible, blocking statements are encountered before the thread gets a chance to die.

The stopRequest() method simply sets the noStopRequested flag to false (line 95) and interrupts the internal thread (line 96) in case it is blocked on an interruptible statement. In this example, the internal thread spends most of its time blocked sleeping, and the interrupt will wake it up early to take notice of the stop request. The isAlive() method is used after a stopRequest() call to check if the thread has died yet.

The paint() method (lines 103–105) is invoked by the event handling thread whenever there has been a request to repaint the component. In this case, paint simply draws the image indicated by currFrame onto the component (line 104).

The class SquishMain, shown in Listing 11.6, creates support containers to demonstrate the functionality of two Squish components.

Example 11.6. SquishMain.java—Code to Demonstrate the Use of Squish

 1: import java.awt.*;
 2: import java.awt.event.*;
 3: import javax.swing.*;
 4:
 5: public class SquishMain extends JPanel {
 6:     public SquishMain() {
 7:         Squish blueSquish = new Squish(150, 150, 3000L, 10, Color.blue);
 8:         Squish redSquish = new Squish(250, 200, 2500L, 10, Color.red);
 9:
10:         this.setLayout(new FlowLayout());
11:         this.add(blueSquish);
12:         this.add(redSquish);
13:     }
14:
15:     public static void main(String[] args) {
16:         SquishMain sm = new SquishMain();
17:
18:         JFrame f = new JFrame("Squish Main");
19:         f.setContentPane(sm);
20:         f.setSize(450, 250);
21:         f.setVisible(true);
22:         f.addWindowListener(new WindowAdapter() {
23:                 public void windowClosing(WindowEvent e) {
24:                     System.exit(0);
25:                 }
26:             });
27:     }
28: }

In main() (lines 15–27), a new SquishMain object is constructed (line 16) and placed into the content pane of a JFrame (line 19). The frame is sized and made visible (lines 20–21). Event-handling code is added to the frame so that when its Close control is clicked, the VM will exit (lines 22–26).

The SquishMain class is a subclass of JPanel (line 5) so that it can have components added to it. In the constructor (lines 6–13), two Squish components are constructed: one blue, and one red (lines 7–8). Because the Squish components each automatically start an internal thread to animate themselves, no further action is required to use them other than simply constructing them! The objects are a subclass of JComponent and are simply added to the panel within a FlowLayout (lines 10–12).

Figures 11.1 and 11.2 show two snapshots of SquishMain in action.

Snapshot of SquishMain running with both mounds relatively tall.

Figure 11.1. Snapshot of SquishMain running with both mounds relatively tall.

Another snapshot of SquishMain running, this time with both mounds a bit flatter.

Figure 11.2. Another snapshot of SquishMain running, this time with both mounds a bit flatter.

Summary

In this chapter, I showed you a technique for creating active classes that are self-running. Self-running classes automatically create an internal thread that runs inside the object. This thread is usually started in the constructor, but can be started later if necessary. Users of self-running objects do not need to concern themselves with the details of creating and starting a thread for an object—in fact, they don't even need to know that a thread is running inside it at all. Additionally, because the users of a self-running object do not have a reference to the internal thread, they cannot erroneously use deprecated methods to suspend or stop the thread at a bad time.

Of the two self-running designs presented, I recommend that you use the one that hides run() within an anonymous, inner class. This design has the advantage of preventing a user from mistakenly invoking run() directly, or from mistakenly creating a new thread that invokes run().

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.140.88