You are probably familiar with multitasking if you have an operating system such as Windows 95 or NT. Multitasking is having multiple programs perform tasks concurrently. For example, you could play a game while a document is printing and another program is performing a lengthy calculation, such as finding all primes up to a million.
Multithreading takes this one further step. A thread is a path of execution in a program. Multithreading allows your program to do two things at once (or three things, or ten...whatever). A common application of multithreading in applications is to perform a task in the background, such as printing, while the user continues to work on the document. In Java, multithreading lets your applet do stuff while the browser does its. This is great for animations, video, and other multimedia that require continuous updating. This is also great for ClockApplet, which needs a mechanism to display the time every second.
An applet supports multithreading by implementing the Runnable
interface. What's that again? An interface is a "contract for services" that a class implementing it must provide. That is, a class that implements an interface is expected to (and indeed must) define all the methods specified by the interface. (Note to C++ programmers: Interfaces are like purely abstract classes.) A class X
that implements an interface Y
is a Y
, and can be treated as such in your program.
Let's see how an applet implements the Runnable
interface. Make the following modification to the class declaration of ClockApplet
:
public class ClockApplet extends Applet implements Runnable {
The implements
keyword followed by the interface name does the trick. You're not done yet though. As explained above, you now need to define all the methods of Runnable
as methods in ClockApplet
. (There's only one method defined in Runnable
: run()
.) Before we do that, first add a couple statements to the Fields section:
// // Fields // private Thread thread = new Thread(this); private Date now = new Date();
The first statement creates a new Thread
object called thread
. Note the keyword this
: this references the class it's contained in, which in this case is ClockApplet
. Passing this
to Thread
tells Thread
that this
contains a run()
method. (This is the reason why ClockApplet
needs to implement the Runnable
interface. Thread
needs to make sure that the class has a run()
method, because Thread
will call it.) The second statement creates a Date
object called now
that is initialized to the current date and time. (A Date
object does not update itself continuously though.)
The run()
method, along with several others, is defined as follows (add this after the update()
method):
// // Thread functions // public void start() { if(thread.isAlive()) thread.resume(); else thread.start(); } public void run() { while(true) { now = new Date(); // Get current time repaint(); try { thread.sleep(500); } catch(InterruptedException e) { } } } public void stop() { thread.suspend(); } public void destroy() { thread.stop(); }
The run()
method is the sole method required when implementing Runnable
. Before we get to that, let's examine the applet life cycle.
There are four stages in the life cycle of an applet. These are:
init()
method to perform one-time initialization, as we've been doing for both PaintApp
and ClockApplet
.start()
method to respond to this. (Note that start()
may be called many times in the applet's lifetime.)stop()
method to respond to this. (Note that stop()
may be called many times in the applet's lifetime.)destroy()
method to clean up the applet.We override the start()
, stop()
, and destroy()
methods to control the execution of our new thread. Here they are again:
public void start() { if(thread.isAlive()) thread.resume(); else thread.start(); } public void stop() { thread.suspend(); } public void destroy() { thread.stop(); }
In the start()
method, we first call the isAlive()
method of thread
to check if the thread is "alive." When the applet first starts, the thread is "dead," so we call thread.start()
to start it running. (This in turn calls our run()
method, which we'll look at in a minute.) If the applet is restarted after having been suspended though, the thread is already alive, so we call thread.resume()
to simply resume execution.
In the stop()
method, we call thread.suspend()
to suspend the thread's execution. After all, the clock doesn't need updating when the user isn't looking at it! Finally, we call thread.stop()
in the destroy()
method to kill the thread, once and for all.
run
in DetailWe finally get to the heart of the thread, the run()
method:
public void run() { while(true) { now = new Date(); // Get current time repaint(); try { thread.sleep(500); } catch(InterruptedException e) { } } }
Ah, a new keyword! The while
statement executes a body of code while a condition is true
. Here is the syntax:
while(boolean-expression) loop-body
If boolean-expression
is true
, loop-body
is executed. Then boolean-expression
is checked again, and so on. In our run()
method, since true
is always true
, we've created an infinite loop. (It's not all that infinite though; Java's threading mechanism can break the loop if we call thread.stop()
.)
Inside the loop, we create a new Date
object and assign it to now
. Using new Date()
always gets the current date and time. We call repaint()
to update the clock display, and then call the sleep()
method of thread
to suspend it for 500 milliseconds, or half a second. The sleep()
method can throw an InterruptedException
, so we need to catch
it. You can simply ignore the exception, as we do here.
Why do we update the clock every half a second? We could update it every second (by using 1000 instead of 500), but the timing is not completely precise. Using 500 provides a better "resolution."
If you compile the applet now, nothing will move. That's because we haven't drawn the clock hands yet. In preparation for that, add the following statement to the paint()
method:
public void paint(Graphics g) { drawFace(); g.drawImage(faceBuffer, 0, 0, null); }
The new drawFace()
method draws the clock hands (add this after createFace()
):
// Draw all clock hands private void drawFace() { Graphics g = faceBuffer.getGraphics(); g.drawImage(tickImage, 0, 0, null); }
This calls the getGraphics()
method of faceBuffer
to get a Graphics
object that's linked to it, then calls drawImage()
to copy the contents of tickImage
onto it.
If you want, you can compile the applet and test it. Although nothing seems to have changed, the clock face is in fact being updated every half a second. In the next step we'll flesh out drawFace()
.