So far, we have a simple knowledge
of working with threads: we know how to use the
start()
method to start a thread, and how to
terminate a thread by arranging for its run()
method to complete. We’ll now look at two techniques that
provide us more information about the thread during its life cycle.
There is a period of time after you call
the start()
method before the virtual machine
can actually start the thread. Similarly, when a thread returns from
its run()
method, there is a period of time
before the virtual machine can clean up after the thread; and if you
use the stop()
method, there is an even greater
period of time before the virtual machine can clean up after the
thread.
This delay occurs because it takes time to start or terminate a
thread; therefore, there is a transitional period from when a thread
is running to when a thread is not running, as shown in Figure 2.3. After the run()
method
returns, there is a short period of time before the thread stops. If
we want to know if the start()
method of the
thread has been called—or, more usefully, if the thread has
terminated—we must use the isAlive()
method. This method is used to find out if a thread has actually been
started and has not yet terminated:
Determines if a thread is considered alive. By definition, a thread is considered alive from sometime before a thread is actually started to sometime after a thread is actually stopped.
Let’s modify our Animate class to wait until the timer thread stops before finishing:
import java.applet.*; import java.awt.*; public class Animate extends Applet { int count, lastcount; Image pictures[]; TimerThread timer; public void init() { lastcount = 10; count = 0; pictures = new Image[10]; MediaTracker tracker = new MediaTracker(this); for (int a = 0; a < lastcount; a++) { pictures[a] = getImage( getCodeBase(), new Integer(a).toString()+".jpeg"); tracker.addImage(pictures[a], 0); } tracker.checkAll(true); } public void start() { timer = new TimerThread(this, 1000); timer.start(); } public void stop() { timer.shouldRun = false; while (timer.isAlive()) { try { Thread.sleep(100); } catch (InterruptedException e) {} } timer = null; } public void paint(Graphics g) { g.drawImage(pictures[count++], 0, 0, null); if (count == lastcount) count = 0; } }
Just because a thread has been started
does not mean it is actually running, nor that it is able to
run—the thread may be blocked, waiting for I/O, or it may still
be in the transitional period of the start()
method. For this reason, the isAlive()
method is
more useful in detecting whether a thread has stopped running. For
example, let’s examine the stop()
method
of this applet. Just like the earlier versions, we have a TimerThread
object that is started and stopped when the applet is started and
stopped. In this newer version, the applet’s
stop()
method does more than just stop the
TimerThread: it also checks to make sure the thread actually has
stopped.
In this example, we don’t gain anything by making sure the timer thread has actually stopped. But if for some reason we need to deal with common data that is being accessed by two threads, and it is critical to make sure the other thread is stopped, we can simply loop and check to make sure the thread is no longer alive before continuing.
There is another circumstance in which a
thread can be considered no longer alive: if the
stop()
method is called, the thread will be
considered no longer alive a short time later. This is really the
same case: the isAlive()
method can be used to
determine if the run()
method has completed,
whether normally or as a result of the stop()
method having been called.
The isAlive()
method can be thought of as a crude form of communication. We are
waiting for information: the indication that the other thread has
completed. As another example, if we start a couple of threads to do
a long calculation, we are then free to do other tasks. Assume that
sometime later we have completed all other secondary tasks and need
to deal with the results of the long calculation: we need to wait
until the calculations are finished before continuing on to process
the results.
We could accomplish this task by using the looping
isAlive()
technique we’ve just discussed,
but there are other techniques in the Java API that are more suited
to this task. This act of waiting is called a thread
join. We are “joining” with the thread that
was “forked” off from us earlier when we started the
thread. So, modifying our last example, we have:
import java.applet.Applet; public class Animate extends Applet { ... public void stop() { t.shouldRun = false; try { t.join(); } catch (InterruptedException e) {} } }
The Thread class provides the following
join()
methods:
Waits for the completion of the specified thread. By definition,
join()
returns as soon as the thread is
considered “not alive.” This includes the case in which
the join()
method is called on a thread that has
not been started.
Waits for the completion of the specified thread, but no longer than the timeout specified in milliseconds. This timeout value is subject to rounding based on the capabilities of the underlying platform.
Waits for the completion of the specified thread, but no longer than a timeout specified in milliseconds and nanoseconds. This timeout value is subject to rounding based on the capabilities of the underlying platform.
When the join()
method is called, the current
thread will simply wait until the thread it is joining with is no
longer alive. This can be caused by the thread not having been
started, or having been stopped by yet another thread, or by the
completion of the thread itself. The join()
method basically accomplishes the same task as the combination of the
sleep()
and isAlive()
methods we used in the earlier example. However, by using the
join()
method, we accomplish the same task with
a single method call. We also have better control over the timeout
interval, and we don’t waste CPU cycles by polling.
Another interesting point about both the
isAlive()
method and the
join()
method is that we are actually not
affecting the thread on which we called the method. That thread will
run no differently whether the join()
method is
called or not; instead, it is the calling thread that is affected.
The isAlive()
method simply returns the status
of a thread, and the join()
method simply waits
for a certain status on the
thread.