The RunnableTestapplication is an example of how to implement the Runnable interface to create a thread.. To get this thread started, you need to create a new Threadobject called t in th
Trang 1Implementing the Runnable Interface
There is another way to create a thread Instead of extending the Thread class, you can implement the Runnableinterface The Runnableinterface requires that you override one method You guessed it: the run()method To run a Runnable object in a thread, you pass it to the Thread(Runnable)constructor When you invoke start()on the thread, it results in a call to the Runnable’s run()method
The Thread class, itself, implements the Runnable interface That’s why when you extend Thread , you implement the run() method.
The RunnableTestapplication is an example of how to implement the Runnable interface to create a thread Its run()method is exactly the same as the Thread-Testclass’s run()method It just counts to 10 To get this thread started, you need to create a new Threadobject (called t) in the main()method This Runnable object is passed into its constructor and then its start()method is invoked Here
is a listing of the source code:
/*
* RunnableTest
* Demonstrates how to implement the Runnable interface
*/
public class RunnableTest implements Runnable { //must implement the run method
public void run() { for (int i=1; i <= 10; i++) { System.out.println(i);
} } public static void main(String args[]) { RunnableTest test = new RunnableTest();
//Construct a thread with this Runnable Thread t = new Thread(test);
//start the thread t.start();
} }
You can see the output of the RunnableTestapplication in Figure 10.3
What’s the difference between extending Thread and implementing Runnable ? Why would you choose one over the other? Generally, it is better to implement the Runnable interface One good reason for this is that if you are defining a sub-class of another sub-class, such as GUIFrame , you can’t also subclass the Thread
class Inheritance can come from only one direct superclass To get around this
T R I C K
H I N T 358
J a
s o
l ut
n e
Trang 2FIGURE 10.3
The output of the RunnableTest application is exactly the same as the ThreadTest application.
Problems Associated with Multithreading
When two threads are running concurrently, there is no guarantee which thread will be running at any given time The MultiThreadTest application demon-strates this It has two threads: the original program’s execution thread and a sec-ond thread, an instance of MultiThreadTest, that the original thread starts One
of the threads lists letters in order and the other lists numbers The output of this program is not exactly predictable and multiple runs can produce different output Here is the source code:
/*
* MultiThreadTest
* Demonstrates the unpredictability of multithreading
*/
public class MultiThreadTest extends Thread { public void run() {
for (char a='A'; a <= 'J'; a++) { System.out.println(a);
} } public static void main(String args[]) { MultiThreadTest t = new MultiThreadTest();
//fire off the thread t.start();
//continue on simultaneously for (int i=1; i <= 10; i++) { System.out.println(i);
} } } You can see the output of the MultiThreadTestapplication in Figure 10.4
Trang 3This is just a simple example, but I’m sure you can imagine what kinds of disas-ters can result if you expect code to be executed in a particular order and you have multiple threads doing different things simultaneously Your data can become corrupted!
Writing Thread-Safe Code
Java provides ways to write thread-safe code, that is, code that can have multiple
threads executing it simultaneously, and not become unstable because of cor-ruption of some kind You can use the synchronizedkeyword as a modifier for methods This keyword makes sure that only one thread can be executing the method at a time It works by locking this, the object that owns the code Only one thread at a time can own a lock for an object Entering a synchronized method ensures that while a thread is inside it, it has an exclusive lock on this Any other thread that needs to get into this method has to wait until the lock is released When a thread passes out of synchronized code, the lock is automati-cally released
public synchronized void mySafeMethod() { … }
360
J a
s o
l ut
n e
FIGURE 10.4
The MultiThreadTest application ran twice with totally different output.
Trang 4To enter mySafeMethod(), the thread must gain a lock to the instance of the class that defines the method When it gets into mySafeMethod(), it automatically gains the lock to thisand when it passes out of the method, the lock is released, allowing other threads to enter mySafeMethod() You can use the synchronized keyword also to lock blocks of code You can lock on any object:
synchronized (myObject) {
… } myObjectmust not be locked by another thread to enter the block of code defined
in the curly braces Any thread that gets into the curly braces gains a lock to myObject This second method of synchronization is much less commonly used because it can be dangerous to lock any object other than the one that owns the code
volatile is another keyword that has use in multithreaded environments Only variables can be volatile This keyword is not used very often It indicates to the compiler that the variable might be modified out of synch and that multiple threads should be careful when working with it.
Using wait(), notify(), and notifyAll()
The wait(), notify(), and notifyAll()methods allow for threads to be paused and then restarted These methods are defined in the Objectclass They must be called from within synchronized code When a thread enters the wait()method,
it releases the object lock and pauses execution until another thread invokes the notify()or notifyAll()method notify()wakes up one thread waiting on this object and notifyAll()notifies all the threads waiting on this object The wait() method can throw an InterruptedException A thread can be interrupted while
it is waiting or asleep if you invoke the interrupt()method of the Threadclass
public synchronized void getValue() {
if (valueNotSetYet) { try { wait();
} catch (InterruptedException e) { } }
x = value;
} public synchronized void setValue() { value = y;
valueNotSetYet = false;
notify();
}
H I N T
361
Trang 5In the previous snippet of code, one thread is responsible for getting a value that
is set by a different thread Because you can’t predict exactly when the value will
be set, you need to determine whether the value has been set before you try to get it (assume valueNotSetYetis a booleanvariable that indicates whether the value has been set) If the value hasn’t been set, you wait for it Calling wait() releases the lock and allows the second thread to synchronize on thisand set the value valueNotSetYetto false, and then call notify()to allow the first thread
to retrieve the value it so desperately needs to get
Putting a Thread to Sleep
You can pause a thread for a set number of milliseconds by calling the static Thread.sleep(long)method You will see the importance this feature has for ani-mation when you get to that point to pause between each frame of aniani-mation The longparameter specifies the number of milliseconds the thread sleeps (1000 milliseconds is one second) A sleeping thread can be interrupted, so calling Thread.sleep(long)requires that you handle InterruptedExceptions Here is an example of an application that calls Thread.sleep(long) It alternatively prints
“Tic”and “Toc”to standard output, pausing one second in between each print Here is the source code for SleepTest.java:
/*
* SleepTest
* Demonstrates how to make a Thread sleep
*/
public class SleepTest extends Thread { public void run() {
for (int i=0; i < 10; i++) {
if (i % 2 == 0) System.out.println("Tic");
else System.out.println("Toc");
try { Thread.sleep(1000);
} catch (InterruptedException e) {}
} } public static void main(String args[]) { SleepTest t = new SleepTest();
t.start();
} }
Figure 10.5 shows the output
362
J a
s o
l ut
n e
Trang 6Performing Animation
Animation is basically done by showing a series of pictures that differ slightly from each other in a way that makes the image appear to be moving If the pic-tures change fast enough, the animation looks smooth In this section, I’ll take you through the process of using threads to animate sprites
The Sprite Class
A sprite, in its simplest definition, is any graphical image that moves, especially
when in relation to computer games A sprite is typically made up of a series of images that are used to animate a motion It also has an (x, y) position and a direction and speed of its movement The Spriteclass I defined for this chapter
is a simple class that encapsulates these concepts It has an array of images, images[], a Point location, location, and another Point, called deltaPoint, which indicates the direction and speed of the sprite
The way this works is the locationpoint is ignored by deltaPoint deltaPoint indicates the distance the sprite will move in both the xdirection and the y direc-tion from where it currently is, wherever that might be, every time the sprite is updated There is another member, currentImgIndex,which keeps track of the images[] Imageelement (the actual image) that is set as the current image to be displayed The Spriteclass has its getand setmethods for modifying the pro-tected members The constructor accepts the array of images, the starting loca-tion, and also the deltaPoint Pointobject
The update()method sets the current image to the next image (if it gets past the last image, it loops back to the first one), and then it moves the current location based on the value of deltaPointby calling the translate(int, int) method
This method belongs to the Point class and moves the Point object’s location based on the change in xand the change in ythat you pass in as its parameters
363
FIGURE 10.5
To really benefit from this, you kinda sorta have to run it yourself.
Trang 7The Sprite class doesn’t perform any animation on its own It relies on other classes to call its update()method, which creates the illusion of motion Here is the source listing for Sprite.java:
/*
* Sprite
* Encapsulates images for animation.
*/
import java.awt.*;
public class Sprite { protected Image[] images;
protected Point location;
//indicates the change in (x, y) protected Point deltaPoint;
protected int currentImgIndex;
public Sprite(Image[] imgs, Point loc, Point delta) { images = imgs;
currentImgIndex = 0;
location = new Point(loc.x, loc.y);
deltaPoint = new Point(delta.x, delta.y);
} public Image getCurrentImage() { return images[currentImgIndex];
} public void setLocation(Point loc) { location = new Point(loc.x, loc.y);
} public Point getLocation() { return new Point(location.x, location.y);
} public void setDeltaPoint(Point dest) { deltaPoint = new Point(dest.x, dest.y);
} public Point getDeltaPoint() { return new Point(deltaPoint.x, deltaPoint.y);
} public void update() { currentImgIndex = (currentImgIndex + 1) % images.length;
location.translate(deltaPoint.x, deltaPoint.y);
} }
364
J a
s o
l ut
n e
Trang 8Testing the Sprite Class
The SpriteTestclass tests the Sprite class by giving it some images, specifying its location, and delta point, and repeatedly calling its update() method from within a thread Figure 10.6 shows the three images that make up the animation
365
FIGURE 10.6
It’s BoitMan! These images are animated by the SpriteTest class.
Here is the source code for SpriteTest.java: /*
* SpriteTest
* Tests the Sprite class
*/
import java.awt.*;
public class SpriteTest extends GUIFrame implements Runnable { Sprite sprite;
public SpriteTest(Image[] images, Point loc, Point dest) { super("Sprite Animation Test");
MediaTracker mt = new MediaTracker(this);
for (int i=0; i < images.length; i++) { mt.addImage(images[i], i);
} try { mt.waitForAll();
} catch (InterruptedException e) {}
sprite = new Sprite(images, loc, dest);
} public static void main(String args[]) { Image[] imgs = { Toolkit.getDefaultToolkit().getImage("b1.gif"),
Toolkit.getDefaultToolkit().getImage("b2.gif"), Toolkit.getDefaultToolkit().getImage("b3.gif") };
SpriteTest spriteTest = new SpriteTest(imgs, new Point(0, 0),
new Point(3, 3));
spriteTest.setSize(300, 300);
spriteTest.setVisible(true);
Thread runner = new Thread(spriteTest);
runner.start();
}
Trang 9//assumes animation moves to the right, down, or both public void run() {
while (sprite.getLocation().x < getSize().width
&& sprite.getLocation().y < getSize().height) { repaint();
sprite.update();
try { Thread.sleep(50);
} catch (InterruptedException e) {}
} } public void paint(Graphics g) { g.drawImage(sprite.getCurrentImage(), sprite.getLocation().x,
sprite.getLocation().y, this);
} }
It is a GUIFramethat constructs a Spriteobject by passing in an array of images consisting of b1.gif, b2.gif, and b3.gif The second argument to the constructor is new Point(0, 0)so the sprite starts in the top-left corner of the GUIFrame The third argument is new Point(3, 3), so every time the sprite is updated by call-ing its update() method, it will move three generic units (often pixels) to the right and three down
SpriteTestimplements the Runnableinterface and overrides run()to move the sprite until it is no longer in the visible GUIFramearea It calls repaint()to dis-play the image, and then calls sprite.update()to update the sprite to its next frame, sleeps for 50 milliseconds, and then repeats this dance The paint(Graph-ics)method paints sprite’s image at its current location, which it gets by call-ing sprite.getLocation().x and sprite.getLocation().y Figure 10.7 hints at what this looks like; however, you have to run it for yourself to actually see the animation
366
J a
s o
l ut
n e
FIGURE 10.7
Go BoitMan, Go!
Trang 10Double Buffering
Did you notice that ugly flickering while BoitMan was running across the screen? This is because the drawing is taking place directly onto the screen To eliminate the flickering, you need to paint the image to an off-screen buffer first, and then copy the entire off-screen image to the screen This technique is called
double buffering Here’s how it works.
Calling a component’s repaint()method results in a call to the update(Graph-ics)method, which clears the component’s graphics with the background color
You actually see this happening on-screen, which is the main cause of the flick-ering Then the update(Graphics)method sets the color to the foreground color and invokes paint(Graphics) You see the background color for a split second, and then you see the image again This is where the flickering comes from
To prevent the flickering, override update(Graphics)so that it doesn’t clear the background It just calls paint(Graphics)directly Then from paint(), create an image buffer, which is an invisible image You create the off-screen image buffer
by invoking the createImage(int, int)method that is defined in the Component class The arguments are its width and its height Then draw on to the buffered image and then copy the whole thing to the screen The SpriteNoFlickerTest application performs double-buffering:
/*
* SpriteTest
* Tests the Sprite class
*/
import java.awt.*;
public class SpriteNoFlickerTest extends GUIFrame implements Runnable { Sprite sprite;
Image offImg;
public SpriteNoFlickerTest(Image[] images, Point loc, Point dest) { super("Sprite Animation Test (No Flicker)");
MediaTracker mt = new MediaTracker(this);
for (int i=0; i < images.length; i++) { mt.addImage(images[i], i);
} try { mt.waitForAll();
} catch (InterruptedException e) {}
sprite = new Sprite(images, loc, dest);
} public static void main(String args[]) { Image[] imgs = { Toolkit.getDefaultToolkit().getImage("b1.gif"),
Toolkit.getDefaultToolkit().getImage("b2.gif"), Toolkit.getDefaultToolkit().getImage("b3.gif") };
367