Understanding Deployment Frameworks - Managing the Applet Animation Thread
(Page 2 of 4 )
What was once prescribed is now proscribed when it comes to starting and stopping applet animation threads. In the past, the lifecycle method implementations might have looked like this:
public void start( )
//////////////////////////////////////////////////////////////////////
{
animationThread = new Thread ( this );
animationThread.start ( );
}
public void stop( )
//////////////////////////////////////////////////////////////////////
{
animationThread.stop ( );
}
Or even like this:
public void start ( )
//////////////////////////////////////////////////////////////////////
{
if ( animationThread == null )
{
animationThread = new Thread ( this );
animationThread.start ( );
}
else
{
animationThread.resume ( );
}
}
public void stop ( )
//////////////////////////////////////////////////////////////////////
{
animationThread.suspend ( );
}
public void destroy ( )
//////////////////////////////////////////////////////////////////////
{
animationThread.stop ( );
}
The problem with these techniques is that using thejava.lang.Threadclass methodsstop(),suspend(), andresume()can sometimes put an object in an inconsistent state or cause a deadlock. This can result in random side effects that are difficult to debug. For this reason, these methods are now deprecated and should not be used in almost all circumstances. I have seen strange bugs simply disappear as a result of replacing old code that usedThread.stop(). Please see the online article from Sun “Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?”1
public void start ( )
//////////////////////////////////////////////////////////////////////
{
stopRequested = false;
animationThread = new Thread ( this );
animationThread.start ( );
}
public void stop ( )
//////////////////////////////////////////////////////////////////////
{
stopRequested = true;
}
public void run ( )
//////////////////////////////////////////////////////////////////////
{
while ( !stopRequested )
{
animate ( );
}
}
Instead of hard-stopping a thread, you can let it die gracefully by having it poll a boolean stop request flag in a loop as shown in the preceding code. Note that the animation thread does not stop immediately when the request is made, as the stop request flag is only checked once per loop iteration. This can cause a problem if thestart()method is called immediately after thestop()method but before the animation loop has had a chance to poll the flag. It is possible with this technique that with some bad luck and bad timing you might end up with two or more threads running simultaneously.
public void start ( )
//////////////////////////////////////////////////////////////////////
{
animationThread = new Thread ( this );
animationThread.start ( );
}
public void stop ( )
//////////////////////////////////////////////////////////////////////
{
animationThread = null;
}
public void run ( )
//////////////////////////////////////////////////////////////////////
{
Thread animationThread = Thread.currentThread ( );
while ( animationThread == this.animationThread )
{
animate ( );
}
}
The preceding code addresses that issue. Here the test is not a shared boolean flag but rather whether the current thread is still the one that the applet instance has designated as the primary animation thread. This operation is performed using an identity comparison. Note that in therun()method,animationThreadis a local method reference andthis.animationThreadis an object instance reference. In the worst case, the old animation thread only overlaps the new animation thread for one more iteration before bowing out.
public synchronized void start ( )
//////////////////////////////////////////////////////////////////////
{
stopRequested = false;
if ( animationThread == null )
{
animationThread = new Thread ( this );
animatonThread.start ( );
}
else
{
notify ( );
}
}
public synchronized void stop ( )
//////////////////////////////////////////////////////////////////////
{
stopRequested = true;
animationThread.interrupt ( );
}
public void run ( )
//////////////////////////////////////////////////////////////////////
{
while ( animationThread != null )
{
try
{
animate ( );
if ( stopRequested )
{
synchronized ( this )
{
while ( stopRequested )
{
wait ( );
}
}
}
}
catch ( InterruptedException ex )
{
}
}
}
public synchronized void destroy ( )
//////////////////////////////////////////////////////////////////////
{
animationThread = null;
stopRequested = false;
notify ( );
}
The preceding code demonstrates how to prevent old and new animation threads from overlapping even momentarily during the transition by relying upon a single thread that is suspended and resumed using a boolean flagstopRequested and theObjectclasswait()method. Note that the code uses
synchronization, which tends to slow things down. This should not be a major problem because no synchronization requests are within the main animation loop when the animation is not stopped.
Methodsstart(),destroy(), and part of methodrun() are synchronized because any thread that calls thenotify()orwait()method must be the owner of the object monitor. Methodstop()is synchronized because it modifies the shared variablestopRequested.
Note that thestop()method callsanimationThread.interrupt(). This is because theanimate()method in the loop might monitor the interrupted status to determine if it should exit early. It might check the status at successive steps in a lengthy animation-frame generation process. It definitely checks the status if it usesThread.sleep()to delay within theanimate()implementation to slow down the loop to a desired frame rate.
The animation loop exits when thedestroy()method dereferences theanimationThread. Note that the animation loop does not exit immediately whendestroy()is called but only upon the check at the beginning of the next loop iteration. This means that the loop could still be using resources in the next-to-last loop iteration for animation after thedestroy()method has completed. This is most likely to occur if the container calls thedestroy()method immediately after thestop()method. If you intend to add additional code to the precedingdestroy()method to de-allocate resources used during animation, you might need to place some of it at the end of therun()method instead.
Thetry/catchblock within the loop is used to catchInterruptedExceptions thrown either by theanimate()method or by thewait()method. Note that it will probably be thrown most of the time by theanimate()method as theanimationThread.interrupt()call is in thestop()method. Thestop()method is normally not called when the thread is already suspended on thewait().
Calling thestop()method without an intervening call tostart()advances the animation by exactly one frame. This is because thewait()throws anInterruptedExceptionand the loop then continues for half an iteration. It callsanimate()just once before stalling on thewait()command again. Normally, a lifecycle container does not callstop()twice in a row and this single frame advance feature was not planned when the code was designed. You might, however, find it useful nonetheless.
Because I believe that this last technique for animation-thread management is the most robust, I selected it for the design of the reusable animation library described in the following chapter.
Next: Reading from a JAR File >>
More Java Articles
More By Apress Publishing
|
This article is excerpted from chapter two of Advanced Java Game Programming, written by David Wallace Croft (Apress; ISBN: 1590591232). Check it out today at your favorite bookstore. Buy this book now.
|
|