Download Java Multithreading: Differences, Animation, and Synchronization and more Lecture notes Compilers in PDF only on Docsity!
Multithreading
WHAT ARE T HREADS?
INTERRUPTING T HREADS
THREAD STATES
THREAD PROPERTIES
SYNCHRONIZATION
BLOCKING QUEUES
THREAD-SAFE COLLECTIONS
CALLABLES AND F UTURES
E XECUTORS
SYNCHRONIZERS
THREADS AND S WING
You are probably familiar with multitasking in your operating system: the ability to
have more than one program working at what seems like the same time. For example, you
can print while editing or sending a fax. Of course, unless you have a multiple-processor
machine, the operating system is really doling out CPU time to each program, giving the
impression of parallel activity. This resource distribution is possible because although you
may think you are keeping the computer busy by, for example, entering data, much of the
CPU’s time will be idle.
Multitasking can be done in two ways, depending on whether the operating system inter-
rupts programs without consulting with them first or whether programs are only interrupted
when they are willing to yield control. The former is called preemptive multitasking ; the lat-
ter is called cooperative (or, simply, nonpreemptive) multitasking. Older operating systems
such as Windows 3.x and Mac OS 9 are cooperative multitasking systems, as are the operat-
ing systems on simple devices such as cell phones. UNIX/Linux, Windows NT/XP (and
Windows 9x for 32-bit programs), and OS X are preemptive. Although harder to imple-
ment, preemptive multitasking is much more effective. With cooperative multitasking, a
badly behaved program can hog everything.
Multithreaded programs extend the idea of multitasking by taking it one level lower: indi-
vidual programs will appear to do multiple tasks at the same time. Each task is usually
called a thread —which is short for thread of control. Programs that can run more than one
thread at once are said to be multithreaded.
Core Java
So, what is the difference between multiple processes and multiple threads? The essential dif-
ference is that while each process has a complete set of its own variables, threads share the
same data. This sounds somewhat risky, and indeed it can be, as you will see later in this
chapter. However, shared variables make communication between threads more efficient
and easier to program than interprocess communication. Moreover, on some operating sys-
tems, threads are more “lightweight” than processes—it takes less overhead to create and
destroy individual threads than it does to launch new processes.
Multithreading is extremely useful in practice. For example, a browser should be able to
simultaneously download multiple images. A web server needs to be able to serve concur-
rent requests. The Java programming language itself uses a thread to do garbage collection
in the background—thus saving you the trouble of managing memory! Graphical user
interface (GUI) programs have a separate thread for gathering user interface events from
the host operating environment. This chapter shows you how to add multithreading capa-
bility to your Java applications.
Multithreading changed dramatically in JDK 5.0, with the addition of a large number of
classes and interfaces that provide high-quality implementations of the mechanisms that
most application programmers will need. In this chapter, we explain the new features of
JDK 5.0 as well as the classic synchronization mechanisms, and help you choose between
them.
Fair warning: multithreading can get very complex. In this chapter, we cover all the
tools that an application programmer is likely to need. However, for more intricate sys-
tem-level programming, we suggest that you turn to a more advanced reference, such
as Concurrent Programming in Java by Doug Lea [Addison-Wesley 1999].
What Are Threads?
Let us start by looking at a program that does not use multiple threads and that, as a
consequence, makes it difficult for the user to perform several tasks with that pro-
gram. After we dissect it, we then show you how easy it is to have this program run
separate threads. This program animates a bouncing ball by continually moving the
ball, finding out if it bounces against a wall, and then redrawing it. (See Figure 1–1.)
As soon as you click the Start button, the program launches a ball from the upper-left cor-
ner of the screen and the ball begins bouncing. The handler of the Start button calls the
addBall method. That method contains a loop running through 1,000 moves. Each call to move
moves the ball by a small amount, adjusts the direction if it bounces against a wall, and
then redraws the panel.
Ball ball = new Ball(); panel.add(ball);
for (int i = 1; i <= STEPS; i++) { ball.move(panel.getBounds()); panel.paint(panel.getGraphics()); Thread.sleep(DELAY); }
The static sleep method of the Thread class pauses for the given number of milliseconds.
Core Java
- import javax.swing.*;
- /**
- Shows an animated bouncing ball.
- */
- public class Bounce
- {
- public static void main(String[] args)
- {
- JFrame frame = new BounceFrame();
- frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- frame.setVisible(true);
- }
- }
- /**
- A ball that moves and bounces off the edges of a
- rectangle
- */
- class Ball
- {
- /**
- Moves the ball to the next position, reversing direction
- if it hits one of the edges
- */
- public void move(Rectangle2D bounds)
- {
- x += dx;
- y += dy;
- if (x < bounds.getMinX())
- {
- x = bounds.getMinX();
- dx = -dx;
- }
- if (x + XSIZE >= bounds.getMaxX())
- {
- x = bounds.getMaxX() - XSIZE;
- dx = -dx;
- }
- if (y < bounds.getMinY())
- {
- y = bounds.getMinY();
- dy = -dy;
- }
- if (y + YSIZE >= bounds.getMaxY())
- {
- y = bounds.getMaxY() - YSIZE;
- dy = -dy;
- }
- }
- /**
- Gets the shape of the ball at its current position.
- */
- public Ellipse2D getShape()
1 • Multithreading
- return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
- }
- private static final int XSIZE = 15;
- private static final int YSIZE = 15;
- private double x = 0;
- private double y = 0;
- private double dx = 1;
- private double dy = 1;
- }
- /**
- The panel that draws the balls.
- */
- class BallPanel extends JPanel
- {
- /**
- Add a ball to the panel.
- @param b the ball to add
- */
- public void add(Ball b)
- {
- balls.add(b);
- }
- public void paintComponent(Graphics g)
- {
- super.paintComponent(g);
- Graphics2D g2 = (Graphics2D) g;
- for (Ball b : balls)
- {
- g2.fill(b.getShape());
- }
- }
- private ArrayList balls = new ArrayList();
- }
- /**
- The frame with panel and buttons.
- */
- class BounceFrame extends JFrame
- {
- /**
- Constructs the frame with the panel for showing the
- bouncing ball and Start and Close buttons
- */
- public BounceFrame()
- {
- setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
- setTitle("Bounce");
- panel = new BallPanel();
- add(panel, BorderLayout.CENTER);
1 • Multithreading
private BallPanel panel;
public static final int DEFAULT_WIDTH = 450;
public static final int DEFAULT_HEIGHT = 350;
public static final int STEPS = 1000;
public static final int DELAY = 3;
}
- static void sleep(long millis)
sleeps for the given number of milliseconds.
Using Threads to Give Other Tasks a Chance
We will make our bouncing-ball program more responsive by running the code that moves
the ball in a separate thread. In fact, you will be able to launch multiple balls. Each of them
is moved by its own thread. In addition, the AWT event dispatch thread continues running in
parallel, taking care of user interface events. Because each thread gets a chance to run, the
main thread has the opportunity to notice when a user clicks the Close button while the
balls are bouncing. The thread can then process the “close” action.
Here is a simple procedure for running a task in a separate thread:
1. Place the code for the task into the run method of a class that implements the Runnable
interface. That interface is very simple, with a single method:
public interface Runnable { void run(); }
You simply implement a class, like this:
class MyRunnable implements Runnable { public void run() { task code } }
2. Construct an object of your class:
Runnable r = new MyRunnable();
3. Construct a Thread object from the Runnable:
Thread t = new Thread(r);
4. Start the thread.
t.start();
To make our bouncing-ball program into a separate thread, we need only implement a
class BallRunnable and place the code for the animation inside the run method, as in the
following code:
class BallRunnable implements Runnable {
...
java.lang.Thread 1.
Parameters: millis^ The number of milliseconds to sleep
Core Java
public void run() { try { for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.sleep(DELAY); } } catch (InterruptedException exception) { } }
... }
Again, we need to catch an InterruptedException that the sleep method threatens to throw. We
discuss this exception in the next section. Typically, a thread is terminated by being inter-
rupted. Accordingly, our run method exits when an InterruptedException occurs.
Whenever the Start button is clicked, the addBall method launches a new thread (see
Figure 1–2):
Ball b = new Ball(); panel.add(b); Runnable r = new BallRunnable(b, panel); Thread t = new Thread(r); t.start();
Figure 1–2: Running multiple threads
That’s all there is to it! You now know how to run tasks in parallel. The remainder of this
chapter tells you how to control the interaction between threads.
The complete code is shown in Example 1–2.
Core Java
public void run()
{
try
{
for (int i = 1; i <= STEPS; i++)
{
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
}
catch (InterruptedException e)
{
}
}
private Ball ball;
private Component component;
public static final int STEPS = 1000;
public static final int DELAY = 5;
}
/**
A ball that moves and bounces off the edges of a
rectangle
*/
class Ball
{
/**
Moves the ball to the next position, reversing direction
if it hits one of the edges
*/
public void move(Rectangle2D bounds)
{
x += dx;
y += dy;
if (x < bounds.getMinX())
{
x = bounds.getMinX();
dx = -dx;
}
if (x + XSIZE >= bounds.getMaxX())
{
x = bounds.getMaxX() - XSIZE;
dx = -dx;
}
if (y < bounds.getMinY())
{
y = bounds.getMinY();
dy = -dy;
}
if (y + YSIZE >= bounds.getMaxY())
{
y = bounds.getMaxY() - YSIZE;
1 • Multithreading
- dy = -dy;
- }
- }
- /**
- Gets the shape of the ball at its current position.
- */
- public Ellipse2D getShape()
- {
- return new Ellipse2D.Double(x, y, XSIZE, YSIZE);
- }
- private static final int XSIZE = 15;
- private static final int YSIZE = 15;
- private double x = 0;
- private double y = 0;
- private double dx = 1;
- private double dy = 1;
- }
- /**
- The panel that draws the balls.
- */
- class BallPanel extends JPanel
- {
- /**
- Add a ball to the panel.
- @param b the ball to add
- */
- public void add(Ball b)
- {
- balls.add(b);
- }
- public void paintComponent(Graphics g)
- {
- super.paintComponent(g);
- Graphics2D g2 = (Graphics2D) g;
- for (Ball b : balls)
- {
- g2.fill(b.getShape());
- }
- }
- private ArrayList balls = new ArrayList();
- }
- /**
- The frame with panel and buttons.
- */
- class BounceFrame extends JFrame
- {
- /**
- Constructs the frame with the panel for showing the
- bouncing ball and Start and Close buttons
- */
1 • Multithreading
- public static final int DEFAULT_HEIGHT = 350;
- public static final int STEPS = 1000;
- public static final int DELAY = 3;
- }
- Thread(Runnable target)
constructs a new thread that calls the run() method of the specified target.
starts this thread, causing the run() method to be called. This method will return
immediately. The new thread runs concurrently.
calls the run method of the associated Runnable.
You must override this method and supply the instructions for the task that you
want to have executed.
Interrupting Threads
A thread terminates when its run method returns. In JDK 1.0, there also was a stop method
that another thread could call to terminate a thread. However, that method is now depre-
cated. We discuss the reason on page 46.
There is no longer a way to force a thread to terminate. However, the interrupt method
can be used to request termination of a thread.
When the interrupt method is called on a thread, the interrupted status of the thread is set.
This is a Boolean flag that is present in every thread. Each thread should occasionally check
whether it has been interrupted.
To find out whether the interrupted status was set, first call the static Thread.currentThread
method to get the current thread and then call the isInterrupted method:
while ( !Thread.currentThread().isInterrupted() && more work to do ) { do more work }
However, if a thread is blocked, it cannot check the interrupted status. This is where
the InterruptedException comes in. When the interrupt method is called on a blocked
thread, the blocking call (such as sleep or wait) is terminated by an InterruptedException.
There is no language requirement that a thread that is interrupted should terminate.
Interrupting a thread simply grabs its attention. The interrupted thread can decide
how to react to the interruption. Some threads are so important that they should han-
dle the exception and continue. But quite commonly, a thread will simply want to
interpret an interruption as a request for termination. The run method of such a thread
has the following form:
public void run() { try {
...
java.lang.Thread 1.
java.lang.Runnable 1.
Core Java
while (!Thread.currentThread().isInterrupted() && more work to do ) { do more work } } catch(InterruptedException e) { // thread was interrupted during sleep or wait } finally { cleanup, if required } // exiting the run method terminates the thread }
The isInterrupted check is not necessary if you call the sleep method after every work
iteration. The sleep method throws an InterruptedException if you call it when the inter-
rupted status is set. Therefore, if your loop calls sleep, don’t bother checking the inter-
rupted status and simply catch the InterruptedException. Then your run method has the
form
public void run() { try {
... while ( more work to do ) { do more work Thread.sleep( delay ); } } catch(InterruptedException e) { // thread was interrupted during sleep or wait } finally { cleanup, if required } // exiting the run method terminates the thread }
CAUTION: When the sleep method throws an InterruptedException, it also clears the interrupted status.
NOTE: There are two very similar methods, interrupted and isInterrupted. The interrupted method is a static method that checks whether the current thread has been interrupted. Further- more, calling the interrupted method clears the interrupted status of the thread. On the other hand, the isInterrupted method is an instance method that you can use to check whether any thread has been interrupted. Calling it does not change the interrupted status.
Core Java
Each of these states is explained in the sections that follow.
New Threads
When you create a thread with the new operator—for example, new Thread(r)—the thread is
not yet running. This means that it is in the new state. When a thread is in the new state, the
program has not started executing code inside of it. A certain amount of bookkeeping
needs to be done before a thread can run.
Runnable Threads
Once you invoke the start method, the thread is runnable. A runnable thread may or
may not actually be running. It is up to the operating system to give the thread time
to run. (The Java specification does not call this a separate state, though. A running
thread is still in the runnable state.)
NOTE: The runnable state has nothing to do with the Runnable interface.
Once a thread is running, it doesn’t necessarily keep running. In fact, it is desirable if
running threads occasionally pause so that other threads have a chance to run. The
details of thread scheduling depend on the services that the operating system provides.
Preemptive scheduling systems give each runnable thread a slice of time to perform its
task. When that slice of time is exhausted, the operating system preempts the thread and
gives another thread an opportunity to work (see Figure 1–4 on page 27). When selecting
the next thread, the operating system takes into account the thread priorities —see page 19
for more information on priorities.
All modern desktop and server operating systems use preemptive scheduling. However,
small devices such as cell phones may use cooperative scheduling. In such a device, a
thread loses control only when it calls a method such as sleep or yield.
On a machine with multiple processors, each processor can run a thread, and you can have
multiple threads run in parallel. Of course, if there are more threads than processors, the
scheduler still has to do time-slicing.
Always keep in mind that a runnable thread may or may not be running at any given time.
(This is why the state is called “runnable” and not “running.”)
Blocked Threads
A thread enters the blocked state when one of the following actions occurs:
- The thread goes to sleep by calling the sleep method.
- The thread calls an operation that is blocking on input/output , that is, an operation that
will not return to its caller until input and output operations are complete.
- The thread tries to acquire a lock that is currently held by another thread. We discuss
locks on page 27.
- The thread waits for a condition—see page 30.
- Someone calls the suspend method of the thread. However, this method is deprecated,
and you should not call it in your code.
Figure 1–3 shows the states that a thread can have and the possible transitions from one
state to another. When a thread is blocked (or, of course, when it dies), another thread can
1 • Multithreading
be scheduled to run. When a blocked thread is reactivated (for example, because it has
slept the required number of milliseconds or because the I/O it waited for is complete), the
scheduler checks to see if it has a higher priority than the currently running thread. If so, it
preempts the current thread and picks a new thread to run.
Figure 1–3: Thread states
A thread moves out of the blocked state and back into the runnable state by one of the fol-
lowing pathways.
1. If a thread has been put to sleep, the specified number of milliseconds must expire.
2. If a thread is waiting for the completion of an input or output operation, then the oper-
ation must have finished.
3. If a thread is waiting for a lock that was owned by another thread, then the other
thread must relinquish ownership of the lock. (It is also possible to wait with a timeout.
Then the thread unblocks when the timeout elapses.)
4. If a thread waits for a condition, then another thread must signal that the condition
may have changed. (If the thread waits with a timeout, then the thread is unblocked
when the timeout elapses.)
block on I/O
wait
dead
new
runnable
sleep
done sleeping
suspend
resume
notify
I/O complete
run method exits
start
blocked
stop
wait for lock
lock available
1 • Multithreading
Thread Priorities
In the Java programming language, every thread has a priority. By default, a thread inherits
the priority of its parent thread, that is, the thread that started it. You can increase or
decrease the priority of any thread with the setPriority method. You can set the priority to
any value between MIN_PRIORITY (defined as 1 in the Thread class) and MAX_PRIORITY (defined as
10). NORM_PRIORITY is defined as 5.
Whenever the thread-scheduler has a chance to pick a new thread, it prefers threads with
higher priority. However, thread priorities are highly system dependent. When the virtual
machine relies on the thread implementation of the host platform, the Java thread priorities
are mapped to the priority levels of the host platform, which may have more or fewer
thread priority levels.
For example, Windows NT/XP has seven priority levels. Some of the Java priorities will
map to the same operating system level. In the Sun JVM for Linux, thread priorities are
ignored altogether—all threads have the same priority.
Thus, it is best to treat thread priorities only as hints to the scheduler. You should never
structure your programs so that their correct functioning depends on priority levels.
CAUTION: If you do use priorities, you should be aware of a common beginner’s error. If you have several threads with a high priority that rarely block, the lower-priority threads may never execute. Whenever the scheduler decides to run a new thread, it will choose among the high- est-priority threads first, even though that may starve the lower-priority threads completely.
- void setPriority(int newPriority)
sets the priority of this thread. The priority must be between Thread.MIN_PRIORITY and
Thread.MAX_PRIORITY. Use Thread.NORM_PRIORITY for normal priority.
is the minimum priority that a Thread can have. The minimum priority value is 1.
is the default priority of a Thread. The default priority is 5.
is the maximum priority that a Thread can have. The maximum priority value is 10.
causes the currently executing thread to yield. If there are other runnable threads
with a priority at least as high as the priority of this thread, they will be scheduled
next. Note that this is a static method.
Daemon Threads
You can turn a thread into a daemon thread by calling
t.setDaemon(true);
There is nothing demonic about such a thread. A daemon is simply a thread that has no
other role in life than to serve others. Examples are timer threads that send regular
“timer ticks” to other threads. When only daemon threads remain, the virtual machine
exits. There is no point in keeping the program running if all remaining threads are
daemons.
java.lang.Thread 1.
Core Java
- void setDaemon(boolean isDaemon)
marks this thread as a daemon thread or a user thread. This method must be called
before the thread is started.
Thread Groups
Some programs contain quite a few threads. It then becomes useful to categorize them by
functionality. For example, consider an Internet browser. If many threads are trying to
acquire images from a server and the user clicks on a Stop button to interrupt the loading
of the current page, then it is handy to have a way of interrupting all these threads simulta-
neously. The Java programming language lets you construct what it calls a thread group so
that you can simultaneously work with a group of threads.
You construct a thread group with the constructor:
String groupName =.. .; ThreadGroup g = new ThreadGroup(groupName)
The string argument of the ThreadGroup constructor identifies the group and must be unique.
You then add threads to the thread group by specifying the thread group in the thread
constructor.
Thread t = new Thread(g, threadName);
To find out whether any threads of a particular group are still runnable, use the activeCount
method.
if (g.activeCount() == 0) { // all threads in the group g have stopped }
To interrupt all threads in a thread group, simply call interrupt on the group object.
g.interrupt(); // interrupt all threads in group g
However, executors let you achieve the same task without requiring the use of thread
groups—see page 63.
Thread groups can have child subgroups. By default, a newly created thread group becomes
a child of the current thread group. But you can also explicitly name the parent group in the
constructor (see the API notes). Methods such as activeCount and interrupt refer to all threads in
their group and all child groups.
- Thread(ThreadGroup g, String name)
creates a new Thread that belongs to a given ThreadGroup.
- ThreadGroup getThreadGroup()
returns the thread group of this thread.
creates a new ThreadGroup. Its parent will be the thread group of the current thread.
java.lang.Thread 1.
java.lang.Thread 1.
Parameters: g^ The thread group to which the new thread belongs
name The name of the new thread
java.lang.ThreadGroup 1.
Parameters: name^ The name of the new thread group