A thread is a path of execution through a program. Single threaded programs have
one path of execution, and multithreaded programs have two or more paths of
execution. Single threaded
programs can perform only one task at a time, and have to finish each task in sequence
before they can start another. For most programs,
one thread of execution is all you need, but sometimes it makes sense to use
multiple threads in a program to accomplish multiple simultaneous tasks.
For example, a multithreaded math program can let the user set parameters for a new calculation while a previous calculation computes. Or a multithreaded word processor can let the user open a new document while a large file saves or spools to the printer. And sometimes, as you will see in this article, a multithreaded approach is necessary when you need a method to execute in a continuous loop in one thread and another thread to do something else like paint the display between loop iterations.
This article describes multithreaded programming by presenting an example slide show applet. The images for the slide show applet are thumbnails of paintings by Claude Monet downloaded from a public web site. If Claude Monet were alive today, the thumbnails and slide show applet might very well be available from his own web site so prospective buyers can view his latest works and make electronic purchases.
All single or multithreaded programs execute in their own thread created and
started by the underlying Java
VM1. The Java VM also
creates and starts other threads to help manage single or multithreaded
program execution. For example, the Java VM starts the
Finalizer
thread to execute an object's finalize
method before the object is garbage collected, and starts the
AWT-EventQueue-0
thread to call an object's event handling
methods such as actionPerformed
and windowClosing
.
Because the Java VM spawns threads to
handle the execution of a program, you might say that the Java VM itself is
a multithreaded program.
Normally, you do not have to concern yourself with threads created by the Java VM to manage your program execution. As a developer, your primary concern is to determine whether your program works best single threaded or multithreaded, and then design and implement it accordingly. In making your decision, you should take into account that spawning additional threads carries overhead by consuming extra memory and processor resources. But sometimes a multithreaded approach is the best way to go so the user does not have to wait too long for a task to complete before starting another, or as in the case of the slide show applet, for the program to work at all.
The slide show applet downloads three thumbnail paintings by Claude Monet
and draws them one-by-one on the applet's panel. The thumbnail paintings
are stored on a Web site and the slide show applet accesses them by their URL.
To run the slide show applet, get the
SlideShow.java
class file and the
java.policy security policy file.
Compile
the SlideShow.java
class file and create a slides.html
file that looks like this:
<HTML> <BODY> <APPLET CODE=SlideShow.class WIDTH=250 HEIGHT=130> </APPLET> </BODY> </HTML>
Use the applet viewer tool to run the applet as follows:
appletviewer -J-Djava.security.policy= java.policy slides.html
The slide show applet executes in its own thread and spawns
a second thread to retrieve images from an image array, retrieve captions
from a text array, and call the
repaint
method to initiate
drawing the image and caption text. The actual drawing of the image and
text is handled by an event thread started by the Java VM.
Every thread has a name, so you can tell which thread is doing what
by retrieving the current thread and getting its name with the
getName
method in the Thread
class.
When you create threads in your program, you can supply an optional
name, and if you do not supply a name, the Java VM provides a default
name that follows the following naming convention: Thread-0
,
Thread-1
, and so on. Threads created by the Java VM have names
too, as you will see in the sample run below.
The slide show applet uses the
getName
and
System.out.println
methods to show which thread
is executing. Below is a sample slide show applet run that goes
through all the applet's life cycle methods.
As you can see in the sample run, the init
,
start
, stop
, and destroy
life cycle methods execute in thread applet-SlideShow.class
.
The run
method executes in the Timer
thread,
and gets called after the Timer
thread is created and started in the
applet's start
method. The Timer
thread retrieves images and
captions from their arrays and calls the repaint
method to
initiate drawing to the applet's panel. The applet's update
and paint
methods do the actual drawing and execute
in the AWT-EventQueue-0
thread because updating and painting are panel events.
init is thread applet-SlideShow.class start is thread applet-SlideShow.class run is Timer update is AWT-EventQueue-0 paint is AWT-EventQueue-0 stop is thread applet-SlideShow.class start is thread applet-SlideShow.class destroy is thread applet-SlideShow.class
New threads are spawned from threads that are already running.
The Java VM spawns thread applet-SlideShow class
and
AWT-EventQueue-0
to handle applet execution and
panel events, and thread applet-SlideShow class
spawns the Timer
thread. The spawning of the Timer
thread is what makes
this program multithreaded.
On the surface, it might seem like the applet has enough
threads without creating the Timer
thread. After all, the applet's
start
method running in thread applet-SlideShow.class
can retrieve the images and text and call repaint
,
and the update
and paint
methods running
in the AWT-EventQueue-0
thread can do the rendering.
Right? Well, not really.
The browser's VM calls into init
, start
,
stop
, and destroy
and expects them to return
after doing whatever it is they have to do. If you change
the applet so init
or start
or a method
called by init
or start
retrieves the
images and text, the method will execute forever, never return, and put
the applet in a deadlocked state. When that happens the only way to
stop the applet is to issue a kill, interrupt, or break signal from
the command line. The Timer
thread is needed so init
or
start
can return and let the applet paint.
The SlideShow applet implements the following methods:
init
start
stop
destroy
startThread
runWork
setPaused
waitWhilePaused
paint
update
Note: This section describes the slide show applet in terms of threads and how the applet's panel is updated with new images and captions. If you need to brush up on applet basics, see the applets lesson in Essentials of the Java Programming Language: A Hands-On Guide.
The init
method creates the slide show display and arrays
to hold the images and caption text. The images are downloaded
from a public Web site, so a URL class provides the URL for the
images. This method calls the startThread
method, which creates the Timer
thread and leaves
it in a paused state waiting to be notified to begin execution.
public void init() { images = new Image[3]; text = new String[3]; captions = new Label(); setLayout(new BorderLayout()); add(BorderLayout.SOUTH, captions); Label name = new Label("by Claude Monet"); name.setAlignment(Label.CENTER); add(BorderLayout.EAST, name); URL fig = null; try { fig = new URL( "http://developer.java.sun.com:8080/" + "developer/technicalArticles/Threads/applet/"); } catch (java.net.MalformedURLException ex) { System.out.println("Bad URL"); return; } images[0] = getImage(fig, "bordighera.jpg"); images[1] = getImage(fig, "etretat.jpg"); images[2] = getImage(fig, "leyden.jpg"); text[0] = "Garden in Bordighera"; text[1] = "Rock Arch West of Etretat"; text[2] = "Bulbfield and Windmill near Leyden"; printThreadName("init is "); startThread(); }
The printThreadName("init is ")
implementations
gets the name of the current thread and prints that name to
the command line. The sample run above shows that at this point
in the applet's execution the current thread is the applet thread.
init is thread applet-SlideShow.class
The startThread
method is called once when the applet
is first initialized to create timerThread
, initialize
variables, and implement timerThread
behavior. The thread remains in existence until it is stopped
in the applet's destroy
method.
private void startThread() { paused = true; noStopRequested = true; // Use this inner class to hide // the public run method Runnable r = new Runnable() { public void run() { runWork(); } }; timerThread = new Thread(r, "Timer"); timerThread.start(); }
The line timerThread = new Thread(this, "Timer")
means create a thread named Timer
and use this
object as its target runnable. A target runnable is an object
of type Runnable
with a run
method
implementation. So what you are really saying is when the Timer
thread
starts, execute the run
method implementation in
this
(the SlideShow
) object.
The call to timerThread.start()
on the next line
enlivens the Timer
thread by calling the run
method in its
target runnable. A thread remains alive until the run
method returns.
The SlideShow
class uses an inner class to create
the target runnable and hide the public run
method.
An inner class is a class nested or defined
inside another class, and hiding the public run
method
implementation in an inner class like this prevents external code
from erroneously calling it. Because the run
method is
public, using an inner class is safer than having the
SlideShow
class implement the Runnable
interface
and providing the run
method implementation as part
of the SlideShow
class like this:
public class SlideShow extends Applet implements Runnable { . . . public void run() { runWork(); }
An interface defines a set of methods but does not implement their behavior.
Instead, you provide the interface method implementations for the class that
implements the interface. The Runnable
interface provides the
run
method as a common protocol for a class instance to execute
a separate thread of code. Your implementation for the run
method
defines what the separate thread of execution needs to do. In this example,
the run
method implementation calls the runWork
method described in Running The Timer Thread below.
Using the Runnable
interface is one of two approaches
available in the Java programming language for writing multithreaded
program. The other approach is to extend the Thread
class,
which implements the Runnable
interface. However,
because the Java programming language does not let you extend more than
one class, extending the Thread
class has its limitations
when you need to, for example, extend the Applet
class
to write an applet or extend the Frame
class to write
an application with a graphical user interface. For this reason,
using the Runnable
interface is the more common
approach.
The wait-notify mechanism is used to keep the timerThread
in the paused state until the applet's start
method is called. The wait-notify mechanism allows one thread
to wait for a notification from another thread that it can
proceed. The paused
variable is set to
true
in the startThread
method
and checked by the Timer
thread when the run
method
is called. Calls to the applet's start
and
stop
methods toggle the paused
variable
between false
and true
, respectively.
Note that the noStopRequested
instance variable
set to false
in the startThread
method is
declared volatile
. The volatile
modifier
requests the Java VM to always access the shared copy of
the variable so the its most current value is always read.
Making noStopRequested
volatile is necessary so
other threads see the changed value. In this example
thread applet-SlideShow.class
changes the value
in its startThread
and stopThread
methods and the Timer
thread reads the value in its
run
method.
Note: It is important to understand when to use thevolatile
modifier, and there are some rule you can follow. To quote a section from Java Thread Programming: The Authoritative Solution by Paul Hyde:If
- two or more threads access a member variable, and
- one or more threads might change that variable's value, and
- all of the threads do not use synchronization (methods or blocks) to read and/or write the value.
then
that member variable must be declared
volatile
to ensure all threads see the changed value. See Pausing the Applet for information on using synchronization.
The runWork
method moves into a while
loop if no stop has been requested and stays in the loop
until the applet's stopThread
method is called where
the noStopRequested
variable is set to false
.
Inside the loop the waitWhilePaused
method is called
to keep the applet in a paused state if the paused
instance variable has a value of true
.
When the applet first starts, its paused
variable is
initialized to true
and changed to false
when the start
method is called. It is also set to true
when the applet's stop
method is called. So the Timer
thread sits in the while
loop waiting for the
applet's start
and stop
methods
to set the values for the paused
variable.
When paused
is set to false
, the Timer
thread retrieves images and captions from their arrays and calls the
repaint
method to update and paint the applet's panel.
Between calls to the repaint
method and
retrieving the next image and caption, the thread sleeps
for 3 seconds so each image in the slide show can stay
on the display for viewing.
The sleep
method throws InterruptedException
,
which is raised when timerThread.interrupt
is called
from the stopThread
method. The stopThread
method is called by the applet's destroy
method when
the applet exits. The interrupt causes the Timer
thread to
move out of sleep
mode and jump to the catch
block where the interrupt is
reasserted, a message is printed, and the thread dies.
Note that the curFrame
instance variable is declared
volatile
because it is updated by the runWork
method in the Timer
thread and read by the update
method in thread applet-SlideShow.class
.
// Note this method is private private void runWork() { printThreadName("run is "); curFrame = 0; try { while ( noStopRequested ) { waitWhilePaused(); curFrame = ( curFrame + 1 ) % images.length; repaint(); Thread.sleep(3000); } } catch ( InterruptedException x ) { // reassert interrupt Thread.currentThread().interrupt(); System.out.println( "interrupt and return from run"); } }
The call to printThreadName
at the beginning gets
the name of the current thread and prints it
to the command line. The sample run above shows that at
this point in the applet's execution the current thread
is the Timer
thread:
run is Timer
The mechanism for pausing the applet consists of the
pauseLock
object, paused
instance variable,
setPaused
method, and waitWhilePaused
method.
The paused
instance variable is not declared volatile
because all threads that update or read it use synchronization.
Synchronization lets one thread safely change values that another
thread reads. The setPaused
method executed by
thread applet-SlideShow.class
toggles the paused
variable between true
and false
according to
whether the applet is in a stopped, started, or initialized but not yet
started state. The waitWhilePaused
method executed by
the
Timer
thread reads the value and keeps the applet in a paused state
when the paused
value is set to true
and in an
un-paused state when the paused
value changes to
false
. Because both these methods use synchronization,
the waitWhilePaused
method cannot read the value of
the paused
variable until the setPaused
method finishes setting it and vice versa.
When the Timer
thread calls waitWhilePaused
,
it synchronizes on pauseLock
and reads the value of
paused
. If the value is true
,
pauseLock.wait
is called and the Timer
thread
goes to sleep. What is happening is the Timer
thread is waiting
on the pauseLock
object to prevent
the waitWhilePaused
method from returning.
If the value is false
, the while
loop in
the Timer
thread continues to execute.
If the Timer
thread is put to sleep, it
stays asleep until it is either interrupted or notified by the
setPaused
method that paused
is
now false
. When thread applet-SlideShow.class
calls setPaused
, it synchronizes on pauseLock
,
changes the value of paused
, notifies all waiting threads
the value has changed, exits the synchronized block, and returns.
When the Timer
thread receives the notification, it wakes up,
exits its synchronized block, and returns from the
waitWhilePaused
method.
private void setPaused(boolean newPauseState) { synchronized ( pauseLock ) { if ( paused != newPauseState ) { paused = newPauseState; pauseLock.notifyAll(); } } } private void waitWhilePaused() throws InterruptedException { synchronized ( pauseLock ) { while ( paused ) { pauseLock.wait(); } } }
When code synchronizes on an object, it acquires an object-level lock on that object. This means no other thread can call methods on that object until the lock is released.
The wait
methods of the Object
class require
that an object-level lock be held before they are called. Very soon after
the thread gets inside the native code of the wait
method,
the Java VM releases the lock that thread was holding.
Once the waiting thread is notified, times out, or is interrupted,
it competes with other threads to reacquire the object-level lock. Once
the thread has exclusive access to the object-level lock, it returns from
the wait
method and continues inside the synchronized block.
A thread must hold an object-level lock on the object before it can call
any of the wait
, notify
, or notifyAll
methods on that object. Once a thread gets inside wait
, the
Java VM temporarily releases the lock to ensure that the signaling is
solid and not open to any race conditions. And if the waiting thread did
not release the lock, another thread would not be able to call
notify
or notifyAll
because it would not
be able to get the lock.
The repaint
method called from within the run
method
repaints the applet's
panel and calls the applet's paint
method to completely redraw
the applet's panel.
public void paint(Graphics g) { Thread t = Thread.currentThread(); String paint = t.getName(); System.out.println("paint is " + paint); } public void update(Graphics g) { g.drawImage(images[curFrame], 0, 0, this); captions.setText(text[curFrame]); Thread t = Thread.currentThread(); String update = t.getName(); System.out.println("update is " + update); }
The call to printThreadName
at the beginning gets
the name of the current thread and prints it
to the command line. The sample run above shows that at
this point in the applet's execution the current thread
is the AWT-EventQueue-0 thread:
update is AWT-EventQueue-0 paint is AWT-EventQueue-0
The applet's stop
method is called whenever
you minimize applet viewer, or if the applet is running in a
Web page, whenever you go to another Web page. At this point
the setPaused
method is called to put the applet
in a paused state.
public void stop() { setPaused(true); printThreadName("stop is "); }
The last line of the stop
method calls
printThreadName
to get the name of the current thread and print it
to the command line. The sample run above shows that at
this point in the applet's execution the current thread
is the applet thread:
stop is thread applet-SlideShow.class
When you exit applet viewer or close the browser, the applet's
destroy
method is called to stop the thread and
perform cleanup.
The image and text arrays are set to null
to make
them ready for garbage collection.
The call to images[i].flush
flushes all resources used by the
Image
array, including any pixel data being cached for
rendering to the screen and any system resources used to store data or
pixels for the images. The images are reset to a state similar
to when they were first created so that if they are rendered again,
the image data has to be recreated or fetched from its source.
public void destroy() { stopThread(); for (int i = 0; i < images.length; i++) { images[i].flush(); images[i] = null; text[i] = null; } images = null; text = null; printthreadname("destroy is "); } }
The last line of the destroy
method calls
the printThreadName
method to get
the name of the current thread and print the name
to the command line. The sample run above shows that at
this point in the applet's execution the current thread
is the applet thread:
destroy is thread applet-SlideShow.class
While it is relatively easy to get started with multithreaded programming in the Java programming language, mastering the use of multiple threads and establishing communications among them takes time, practice, and patience. An examples-based approach is one of the best ways to learn because you reinforce what you read by putting the concepts into practice.
1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.
© 1994-2005 Sun Microsystems, Inc.