Moving Those Fish


Applications that draw using sprites often do their drawing in a new execution streamthat is, a new thread. The reason for this is because while the new thread draws the sprites and moves them around, the main thread in the application can be doing other thingssuch as, in this application, closing the window when the user clicks the close button.

The drawing and graphics work that goes on behind the scenes will take place in this new thread. To handle a new thread, you make sure that an application implements the Runnable interface:

 import java.awt.*; import java.awt.event.*; public class Aquarium extends Frame implements Runnable {         .         .         . } 

The Runnable interface has only one method, run, which is called when you start a new Thread object connected to the current object. Here's how you create and start that new thread, a Thread object named thread:

 import java.awt.*; import java.awt.event.*; public class Aquarium extends Frame implements Runnable {     Image aquariumImage, memoryImage;     Image[] fishImages = new Image[2];     MediaTracker tracker;     Thread thread;    Aquarium()    {         setTitle("The Aquarium");         .         .         .         memoryImage = createImage(getSize().width, getSize().height);         memoryGraphics = memoryImage.getGraphics();         thread = new Thread(this);         thread.start();        this.addWindowListener(new WindowAdapter(){             public void windowClosing(                 WindowEvent windowEvent){                     System.exit(0);                 }             }         );     }     .     .     . } 

You can see the significant methods of the Thread class in Table 1.4.

Table 1.4. The Significant Methods of the java.lang.Thread Class

Method

Does This

static int activeCount()

Returns the number of threads that are active in this thread's thread group

void checkAccess()

Checks to see if the current running thread can modify a specific thread

static Thread currentThread()

Returns a reference to the current Thread object

void destroy()

Deprecated. This method was supposed to destroy a thread. However, it was decided that doing so could leave system resources locked by this thread being inaccessible. See the discussion in the text for an alternative

static void dumpStack()

Displays a stack trace showing stack use by the current thread

static Map<Thread,StackTraceElement[]> getAllStackTraces()

Returns stack traces for all active threads

static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()

Returns the default exception handler used when a thread terminates because an exception was not caught

long getId()

Returns the ID of this thread, allowing you to identify it

String getName()

Returns the name of this thread

int getPriority()

Returns the priority of this thread

Thread.State getState()

Returns the current state of this thread

ThreadGroup getThreadGroup()

Returns the thread group this thread is a member of

Thread.UncaughtExceptionHandler getUncaughtExceptionHandler()

Returns the exception handler used when a thread terminates because an exception was not caught

static boolean holdsLock(Object obj)

Returns a value of true if the current thread has a lock on the given object

void interrupt()

Interrupts the thread

static boolean interrupted()

Returns true if the current thread has been interrupted

boolean isAlive()

Returns true if this thread is alive

boolean isDaemon()

Returns true if this thread is a daemon thread

boolean isInterrupted()

Returns TRue if this thread has been interrupted

void join()

Lets you join execution streams by waiting for this thread to die

void join(long millis)

Lets you join execution streams by waiting at most the given number of milliseconds for this thread to die

void join(long millis, int nanos)

Lets you join execution streams by waiting at most the given number of milliseconds plus the given number of nanoseconds for this thread to die

void resume()

Deprecated. This method originally was supposed to suspend the execution of a thread. However, it was decided that doing so could leave system resources locked by this thread being inaccessible. See the discussion in the text for an alternative

void run()

Executes the run method if the objected used to construct this thread implements the Runnable interface

static void setDefaultUncaughtExceptionHandler (Thread.UncaughtExceptionHandler eh) void setName(String name)

Sets the default handler used when a thread terminates because of an uncaught exception Sets the name of this thread to the given name

void setPriority(int newPriority)

Sets the priority of the thread

void setUncaughtExceptionHandler(Thread. UncaughtExceptionHandler eh)

Sets the exception handler to use when this thread terminates because of an uncaught exception

static void sleep(long millis)

Makes the thread temporarily suspend execution for the given number of milliseconds

static void sleep(long millis, int nanos)

Makes the thread temporarily suspend execution for the given number of milliseconds plus the given number of nanosecond

void start()

Executes the thread's code by making Java call the thread's run method

void stop()

Deprecated. This method was originally supposed to stop a thread. However, it was decided that doing so could leave system resources locked by this thread being inaccessible. See the discussion in the text for an alternative

void stop(Throwable obj)

Deprecated. This method was originally supposed to stop a thread. However, it was decided that doing so could leave system resources locked by this thread being inaccessible. See the discussion in the text for an alternative

void suspend()

Deprecated. This method was originally supposed to suspend a thread's execution. However, it was decided that doing so could leave system resources locked by this thread being inaccessible. See the discussion in the text for an alternative

static void yield()

Makes this thread pause and yield control temporarily to other threads, letting them execute


To implement the Runnable interface and support the new thread, you have to add a run method with the code you want to execute in the new thread to the current object. When the thread.start method is called, the run method is called, executing the thread's code.

To create and draw the sprites in this application, add the run method to the Aquarium class. To initialize the fish sprites, the code starts by determining the actual edges of the container they're in, which means determining the dimensions of the client window (the area inside all the toolbars and borders) of the current window. You can determine that area using various methods inherited by the Frame classjava.awt.Frame is built on top of java.awt.Window, which is built on top of java.awt.Container, which in turn is built on top of java.awt.Component. In this case, you can use the Component method getSize and the Container method getInsets to determine the actual dimensions in which the fish have to swim around, which is stored in a Java Rectangle object named edges:

 public class Aquarium extends Frame implements Runnable {     Image aquariumImage, memoryImage;     Image[] fishImages = new Image[2];     Thread thread;     MediaTracker tracker;     Graphics memoryGraphics;    Aquarium()    {         .         .         .     }     public void run()    {        Rectangle edges = new Rectangle(0 + getInsets().left, 0             + getInsets().top, getSize().width - (getInsets().left             + getInsets().right), getSize().height - (getInsets().top             + getInsets().bottom));         .         .         . } 

Next in run, the code creates the fish sprites. Each fish will be represented by a Fish object that will do all the fish needs to doswim and draw itself in the aquarium. To keep track of the Fish objects, you'll need to store them in a Java Vector object named fishes, and the number of fish will be stored in an int named numberFishes:

 public class Aquarium extends Frame implements Runnable {     Image aquariumImage, memoryImage;     Image[] fishImages = new Image[2];     Thread thread;     MediaTracker tracker;     Graphics memoryGraphics;     int numberFish = 12;     Vector<Fish> fishes = new Vector<Fish>();         .         .         . 

That's how things look if you're using Java 1.5 (the default for this book), where you need to specify the type of objects in a Vector; if you're using Java 1.4, declare the fishes vector this way:

 Vector fishes = new Vector(); 

Next, you create all the fish. The Fish class's constructor needs the two images for each fish (left-pointing and right-pointing), the edges rectangle so the fish knows the bounds of the aquarium, and a pointer to the Aquarium object itself so the fish knows where it's supposed to draw itself. Here's how the run method creates the fish, passing those values to the Fish class's constructor:

 public void run()    {         for (int loopIndex = 0; loopIndex < numberFish; loopIndex++){             Rectangle edges = new Rectangle(0 + getInsets().left, 0                 + getInsets().top, getSize().width - (getInsets().left                 + getInsets().right), getSize().height - (getInsets().top                 + getInsets().bottom));     for (int loopIndex = 0; loopIndex < numberFish; loopIndex++){         fishes.add(new Fish(fishImages[0], fishImages[1], edges, this));         try {             Thread.sleep(20);         }         catch (Exception exp) {             System.out.println(exp.getMessage());         }     }         .         .         . } 

That's the loop that actually creates the Fish objects. Want more fish? Just change the value assigned to numberFish in that variable's declaration. Try this for a blast:

 int numberFish = 120; 

Note also that after each fish is created and added to the fishes vector, the code calls the THRead.sleep method. This method makes the thread pause by the number of milliseconds you passin this case, that's 20 milliseconds. The code pauses for 20 milliseconds between creating various fish because each fish uses the system time to generate its random position and velocity, and some systems can't resolve time periods less than thisif you simply created one fish immediately after the other, some would end up with exactly the same coordinates and velocity (which means one fish might be hidden behind another, even after they bounced off walls and kept moving around the aquarium).

After the fish are created, all you have to do is to keep calling their swim method to have them move around the tank. Here's what that looks likenote that you can adjust how fast the fish move by changing the value assigned to the sleepTime variable, which is how long the thread sleeps between moving individual fish:

 public class Aquarium extends Frame implements Runnable {     Image aquariumImage, memoryImage;     Image[] fishImages = new Image[2];     Thread thread;     MediaTracker tracker;     Graphics memoryGraphics;     int numberFish = 12;     int sleepTime = 110;     Vector<Fish> fishes = new Vector<Fish>();         .         .         . public void run()    {     for (int loopIndex = 0; loopIndex < numberFish; loopIndex++){             Rectangle edges = new Rectangle(0 + getInsets().left, 0             + getInsets().top, getSize().width - (getInsets().left             + getInsets().right), getSize().height - (getInsets().top             + getInsets().bottom));         fishes.add(new Fish(fishImages[0], fishImages[1], edges, this));         try {             Thread.sleep(20);         }         catch (Exception exp) {             System.out.println(exp.getMessage());         }     }     Fish fish;     while (runOK) {             for (int loopIndex = 0; loopIndex < numberFish; loopIndex++){             fish = (Fish)fishes.elementAt(loopIndex);             fish.swim();         }         try {             Thread.sleep(sleepTime);         }         catch (Exception exp) {             System.out.println(exp.getMessage());         }         repaint();     } } 

REAL-WORLD SCENARIO: TYING UP LOOSE THREADS

Threads are handled differently on different systems, and unless you make some provision for ending a thread, it can hang around foreverand if you get enough of them, the Java runtime will run out of heap space, and the system may even hang, or at least slow waaaaay down.

In the old days, you could stop a thread using its stop method (there were also plans for a destroy method, but that method was never implemented), but that method is now deprecated. The reason is that the creators of Java were concerned that if the thread tied up some system resource, such as a file, and then was stopped, there would no longer be any way to reach that resource.

Their solution was to say that the programmer is responsible for stopping the thread, and that's done by having control return from the run method. In the same way, the suspend and resume methods, which paused and restarted threads, were deprecated. Now the programmer is responsible for pausing and restarting threads himself, which is typically done by setting Boolean variables and making the thread stop working temporarily if so required.

On some systems, threads are launched as their own processes, and sometimes, they can get away from you if you're not careful. Some years ago, I was working on my own commercial site, on one of the major hosting companies, and I wrote some code that intentionally spawned new processes. The problem was that they were never ended properly (even though the code intended to end them when they were done, it didn't terminate them as it should), so my applications just happily went on spawning endless new processes.

That was on Friday night. When I came back to take a look on Sunday, I found that my code had single-handedly brought down the entire machine over the weekendand the server was the host for two international banks, as well as a major airline.

Ah, well!

The moral is, if you don't want your account locked, all your files disabled, and a lot of nasty, frantic emails from system operators threatening legal action, be careful about what you do with threads and not ending spawned processes.


Note the runOK variable herethe while loop keeps calling the swim method of each fish while this variable is true. What's runOK all about? It's the variable used to end the thread when the application is ended. You add the declaration of this variable to the code this way:

 public class Aquarium extends Frame implements Runnable {     Image aquariumImage, memoryImage;     Image[] fishImages = new Image[2];     Thread thread;     MediaTracker tracker;     Graphics memoryGraphics;     int numberFish = 12;     int sleepTime = 110;     Vector<Fish> fishes = new Vector<Fish>();     boolean runOK = true;         .         .         . 

And when the user closes the window, the application will set runOK to false before actually ending. Therefore, add the following code:

 this.addWindowListener(new WindowAdapter(){      public void windowClosing(          WindowEvent windowEvent){              runOK = false;              System.exit(0);          }      }  ); 

That'll end the thread simply by making the run method finish and return. This way, we get rid of the thread when the application ends.



    Java After Hours(c) 10 Projects You'll Never Do at Work
    Java After Hours: 10 Projects Youll Never Do at Work
    ISBN: 0672327473
    EAN: 2147483647
    Year: 2006
    Pages: 128

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net