| Home > Core Java FAQ
> Threads FAQ |
| Threads |
| Creating
and Controlling (07) * Thread
Interactions(10) * User
Threads versus System Threads(04) |
| |
|
Q .
Why is thread synchronization important for multithreaded programs?
|
Ans :
Thread
synchronization provides the tool for ensuring that different
threads take proper turns when using shared resources.
In
general, multiple threads in a program run independently of one
another. Each follows its sequence of commands, blithely ignorant
of other threads or their activities. As long as a thread works
with its own, separate data, the most it might suffer from other
threads is to be slowed down because of sharing CPU time, if the
number of threads is greater than the number of available
processors.
A key property of threads, however,
is their ability to directly access shared data. This ability is
both a blessing and a curse because it allows threads to:
- communicate
efficiently with each other by manipulating shared data
- interfere
capriciously with each other by manipulating shared data
When
threads act on shared data, timing is the crucial issue. You must
always consider how one thread might disrupt the data used by
another thread, by executing some of its instructions in between
the instructions that the other is executing. (This potential for
interleaved execution is true on both single-processor and
multiprocessor platforms.) Even code that appears bullet-proof can
be slain by an intervening thread. Consider, for example, the getTitleLength
method in the following class definition:
public class SomeClass {
String title = "Default Title";
int getTitleLength() {
if (title != null) {
return title.length();
}
return -1; // signal error condition
}
void setTitle(String s) {
title = s;
}
void deleteTitle() {
title = null;
}
}
Suppose one thread
(thread X) is about to run getTitleLength, and
another thread (thread Y) is about to run deleteTitle.
Thread X, entering getTitleLength, checks that the
title string is non-null before invoking length()
on it. Thread Y, however, can interpose between these two steps
and invoke deleteTitle. The title string is now null,
and thread X's next step will invoke the length
method on a null reference—just what the if
statement was supposed to prevent!
Safe multithreaded use of data
requires that different threads must operate either on separate
data or at different times. Since threads must often share data,
the Java programming language provides the synchronized
keyword to mark methods or blocks of code that cannot overlap each
other in time
|
|
Q . What is a monitor?
|
Ans :
A monitor
is a one-thread-at-a-time set of code-blocks and methods that also
provides a wait-notify service.
When
multiple threads access common, changeable data, you must regulate
the timing of those threads to ensure that one thread doesn't
interfere with another thread's assumptions of data integrity. The
Java platform lets you restrict thread interactions by means of
monitors. A monitor groups a set of code blocks into a single
protected space, such that only one thread at a time can be in
the monitor, that is, executing any of the code under the
monitor's protection.
A monitor uses a mutual exclusion
lock to protect its region of code. A thread must acquire/lock the
lock in order to enter the monitor, and it
relinquishes/unlocks the lock when it exits the monitor.
While a monitor is locked, it blocks any other threads that
attempt to enter it. Monitor locks also allow recursive access:
once a thread has the lock, it can run other code that requires
the same lock, effectively re-entering the monitor one level down.
The lock is not released until the thread holding it completely
exits the monitor at all levels. Only at that point can another
thread enter the monitor.
A monitor also provides a
wait-notify service to threads. A thread in the monitor can invoke
wait, which releases the monitor lock and puts the
thread to sleep until it is awakened by a notifyAll
(or notify) method invocation.
You
can use many different monitors in a single program to guard
different parts of your data. Each monitor is associated with a
specific class or object; this connection is often described in
terms such as "each object has a monitor," or "each
object has a lock." Keep in mind, though, that a monitor
provides no inherent protection to its affiliated object.
It's still up to you to choose carefully which code to include in
monitors in order to protect your data.
Here's an alternate view of the
preceding concept-heavy explanation. Let's call code that belongs
to one or another monitor "protected code," and all
other code "unprotected code." Imagine that each object
in your program owns a unique, reusable ticket. Threads can
execute unprotected code freely at any time—no tickets are
required. Protected code, however, always requires a thread to
have a ticket, and each synchronized statement or method specifies
which object that ticket must come from. The thread holds on to
the ticket while executing the protected code block or method, and
hands the ticket back to its owner object immediately upon exiting
the protected code. Because there is only one ticket per object,
any other threads needing that ticket are forced to wait and then
proceed in an orderly, one-by-one fashion through any protected
portion of code.
|
|
Q . How does the synchronized keyword work?
|
Ans :
The synchronized
keyword marks portions of code as belonging to a monitor:
a protected portion of code that enforces a one-thread-at-a-time
policy.
The
Java Virtual Machine provides monitors as the fundamental
concept for managing thread synchronization. Part of a monitor's
function is to protect a set of code blocks and methods, such that
only one thread at a time can execute any of the code belonging to
the monitor. Each object is associated with a monitor, and you
access a monitor by referring to its affiliated object.
You cannot directly inspect or
manipulate monitors; instead, you define the scope of a monitor
implicitly by specifying the code blocks and methods it contains.
This is the job of synchronized statements and synchronized
methods.
A synchronized statement specifies
an object and a block of code: it flags the code block as
belonging to the specified object's monitor. For example, much of
the JDK 1.1 code for managing AWT (Abstract Window Toolkit)
containment relationships uses the Component.LOCK
object in synchronized statements:
/* in Component.java (JDK 1.1): */
static final Object LOCK = new Object();
// ...
public void validate() {
if (!valid) {
synchronized (Component.LOCK) {
valid = true;
}
}
}
A
synchronized method is like a synchronized statement, except that
it specifies only the block of code. The entire method body
belongs to the target object's monitor (that is, the monitor of
the object the method is invoked on). Following is the example
from, modified to contain one synchronized statement and two
synchronized methods:
public class SomeClass {
String title = "Default Title";
int getTitleLength() {
/* a synchronized statement */
synchronized(this) {
if (title != null) {
return title.length();
}
}
return -1; // signal error condition
}
/* a synchronized method */
synchronized void setTitle(String s) {
title = s;
}
/* another synchronized method */
synchronized void deleteTitle() {
title = null;
}
}
This
code specifies that each SomeClass instance has a
monitor containing (at the least) the if statement
inside of getTitleLength, the entire body of setTitle,
and the entire body of deleteTitle.
Finally, the following are some key
points to remember about the relation between monitors and the synchronized
keyword. They were discussed earlier but deserve one more round of
emphasis:
- Monitors define
synchronization-protected sets of code blocks.
- Monitors are the
fundamental unit of synchronization. Synchronized statements
and synchronized methods merely serve to assign blocks of code
to various monitors.
- Synchronized
code is synchronized only with respect to other synchronized
code in the same monitor. Unsynchronized code pays no
attention to monitors, locks, etc.
- The monitor/lock
associated with an object does not inherently lock
the data in the object. Instead, it is like a per-monitor
access ticket. Monitors fundamentally protect code, not data.
|
|
Q . What objects do static synchronized methods use for locking?
|
Ans :
Methods
declared as static
synchronized are class methods; they must obtain a
lock on the class object.
All
synchronized code is associated with one or another object for
purposes of locking. Although synchronized methods don't
explicitly indicate the object to use for locking, the rule is
straightforward: a synchronized instance method obtains the lock
for the object it's invoked on, and a synchronized class method
obtains the lock for the class object (instance of class Class)
that represents the class.
Synchronized class methods (that is,
static synchronized methods) give you a class object implicitly,
but for static synchronized statements you must provide the class
object explicitly. Two methods provide class objects for you:
- Object's
getClass
method; this requires that you have an instance of the class
on hand or are willing to create one
- Class's
forName
method; this allows you to create a class object from a string
representing the class's fully qualified name
Starting
with the JDK 1.1, you can also access class objects directly from
a class's class field. For example, Thread.class
is a reference to the class object for the Thread
class.
Suppose that you have an instance myTimer
of your Timer class; you can then use the first
technique:
Timer myTimer = new Timer();
void myMethod() {
// ...
synchronized (myTimer.getClass()) {
// ... code that requires synchronization
}
}
Suppose next that you
want to lock on the AWT's Component class. Component is an
abstract class; therefore you cannot create instances of it. In
the JDK 1.0.2, you have no choice but to use Class's forName
method (and deal with the ClassNotFoundException that
forName can throw):
void anotherMethod() {
// ...
try {
synchronized (Class.forName("java.awt.Component")) {
// ... code that requires synchronization
}
} catch (ClassNotFoundException e) {
// ... handle the exception
}
}
In the JDK 1.1, the
preceding Component example can be simplified considerably:
/* using JDK 1.1: */
void anotherMethod() {
// ...
synchronized (Component.class) {
// ... body of synchronized block
}
}
|
|
Q . How do the wait and notifyAll/notify methods enable cooperation between threads?
|
Ans :
They use a
monitor as the middleman: wait
places the invoking thread on the monitor's waiting list for
notifications, and notifyAll (or notify)
instructs the monitor to reactivate all (or one) of the threads in
its notification list.
In
contrast to the synchronized keyword, which helps
threads stay out of each other's way, the wait and notifyAll/notify
methods in class Object permit threads to cooperate
actively. A thread invoking wait voluntarily suspends
its activity, trusting that another thread will invoke notifyAll
or notify to eventually reactivate the waiting
thread. (Two versions of wait let you specify also a
maximum waiting time, such that wait will return even
if no notifications are received.) The working of these methods is
personified in Table 9.1, from the point of view of a thread
invoking the method on an object.
The wait and notifyAll/notify
methods constitute a minimal framework of controlled interaction
between threads. The service provided by these methods is:
- mediated:
monitors must serve as middlemen between waiting threads and
notifying threads; the threads never communicate directly.
- anonymous:
a waiting thread can't specify which thread will
notify/reactivate it, and a notifying thread can't specify
which thread(s) it will reactivate.
- contentless:
a reactivated thread receives no indication of what happened
to trigger the notification. If the thread needs to know why
it was reactivated, it must infer the cause from whatever data
it has access to.
- not
guaranteed to be fair: there is no required order in
which to notify waiting threads. It is possible for a thread
to remain on the wait queue arbitrarily long while other
threads are chosen for notification. (Nevertheless, fairness
is a desirable trait that virtual machine implementations
should aim for.)
Table
9.1: wait, notifyAll, and notify
| Method |
Actions
and constraints |
wait |
I
deactivate myself, and ask this object's monitor to enter
me on its notification waiting list. |
| I
must own the lock on this monitor at the point I invoke
wait, but I release the lock as soon as I start waiting. |
| When
I wake up, I must reacquire the lock; this may take some
time if one or more other threads beat me to it. |
| When
I acquire the lock, wait will return; I can then continue
running. |
notifyAll |
I
tell this object's monitor to reactivate all threads on
its notification waiting list. |
I
must own the lock on this monitor at the point I invoke notifyAll. |
| None
of the awakened threads can run until I release the lock. |
notify |
I
tell this object's monitor to reactivate only one of its
waiting threads (and I can't say which one). |
| I
must own the lock on this monitor at the point I invoke
notify. |
| The
awakened thread can't run until I release the lock. |
Finally,
keep in mind that invoking notifyAll does not wake up
all the threads in an application; it affects only those threads
that are waiting on one specific monitor.
|
|
Q . How do I achieve the effect of condition variables if the Java platform provides me with only wait and notifyAll/notify methods?
|
Ans :
Invoke wait
within a while loop that tests for the condition you're interested
in, and make sure that other threads invoke notifyAll
(or notify) at points where you
think or know the condition will change.
The
idea behind a condition variable is that a waiting thread should
be reactivated when a specified condition arises, but not
otherwise. The Java language does not provide such a service—the
interaction provided by the wait, notifyAll,
and notify methods is anonymous and contentless, and
no conditions are checked prior to reactivating a thread. When a
waiting thread is awakened, it receives no information about who
triggered the wakeup or why. The newly reactivated thread must
determine for itself if the condition it's waiting for has
arrived.
To achieve the effect of a condition
variable, place your invocation of wait inside a while statement.
(Do not use an if statement—the condition
could change while the thread is waiting and it must be
rechecked.) For example, the following Thread
subclass waits until the amount of free memory drops below a
predetermined level and then takes action:
class MemoryWatcherThread extends Thread {
long lowMemoryLine = ...;
public void run() {
Runtime runtime = Runtime.getRuntime();
while (true) {
synchronized (Runtime.class) {
while (runtime.freeMemory() >= lowMemoryLine) {
try {
wait(5000); // wait up to 5 seconds
} catch (InterruptedException e) {
}
}
}
// ... respond to low memory level
}
}
}
(If you're using the
JDK 1.0.2, you need to replace the expression Runtime.class
above with Runtime.getRuntime.getClass().) In this
code, there are two ways a MemoryWatcherThread
instance (call it a watcher thread) can become active. First, the
invocation of wait specifies a maximum waiting time
of five seconds (5000 milliseconds), after which wait
will return. Second, any other thread invoking notifyAll
(or notify) may wake up the watcher thread before
that five-second time limit expires. For whichever reason the
watcher thread reactivates, the thread (after acquiring the
monitor lock) then goes back to the top of the while
loop and checks the free memory level. If the level is not too
low, the watcher thread goes into another wait; otherwise it runs
its code to deal with the low memory level.
Continuing the example, objects from
other classes can effectively request action from a watcher thread
by notifying the monitor in which the watcher thread is waiting:
/* using JDK 1.1: */
class Example {
void checkMemory() {
synchronized (Runtime.class) {
Runtime.class.notifyAll();
}
}
// ...
}
Using notifyAll
ensures that all threads waiting on the monitor will be
notified—it lets each waiting thread decide for itself whether
to reactivate or continue waiting. Using notify, on
the other hand, would wake up only one waiting thread, and quite
possibly the wrong one. In general, you shouldn't use notify
unless you know precisely which single thread will be waiting for
it, or you know that any of the waiting threads could respond
equally well to the notification.
|
|
Q . How do I make one thread wait for one or more other threads to finish?
|
Ans :
Use one of Thread's
join methods.
Threads
are designed for the most part to run independently of one
another. Typically, one thread neither knows nor cares when
another thread finishes executing. If you need one of your threads
to wait for another thread to finish, however, you can use one of
the join methods in the Thread class.
Invoking join involves
two threads even though the method invocation only shows one, as
in thatThread.join(). The second thread is
implicit—it is the currently executing thread. The invocation of
join causes the currently executing thread to suspend
execution until the target thread (thatThread in the
above example) finishes executing. In other words, invoking join
on a thread is like waiting to be notified of that thread's death.
Also, like the wait methods, there
is a family of join methods. The basic no-parameter
method, join(), waits indefinitely until the target
thread dies. You can also specify one or two arguments as a
maximum duration to wait:
join(long
millis): wait no more than the specified number of
milliseconds (thousandths of a second).
join(long
millis, int nanos): wait no more than the specified
number of milliseconds and nanoseconds (billionths of a
second).
A
key difference from wait is that join need not and
should not hold a monitor lock when it is invoked.
For reference, Table summarizes the
methods that can suspend a thread's execution (without irrevocably
terminating it):
Table
: Methods that can Suspend Thread Execution
| Class |
Methods |
Thread |
sleep(long
millis), sleep(long millis, int nanos) |
join(),
join(long millis), join(long millis, int nanos) |
suspend() |
Object |
wait(),
wait(long millis), wait(long millis, int nanos) |
|
|
Q . What do I use the yield method for?
|
Ans :
You invoke yield
in one thread to give other threads more chances to run.
Any
platform that runs more than one thread per processor needs to
have a thread scheduler—a strategy for deciding when to switch
execution from one thread to another, and which other thread to
switch to. A thread scheduler typically regulates turn-taking
according to thread states and thread priorities. The following
thread states distinguish among which threads are executing or
have the potential to execute:
- running:
currently executing
- runnable:
ready to run, but not currently executing
- waiting:
not ready to run; must first be reactivated by a timer, a
notification, or a resume method invocation
- dead:
finished executing, cannot run anymore
By
definition, only the running and runnable threads are considered
when the scheduler picks the next thread to run.
Thread priorities
further differentiate among the threads waiting to run. Whenever a
thread scheduler has a choice between a lower and higher-priority
thread, it is supposed to prefer the higher-priority one. There
are no guarantees, though. Thread priorities should
be considered at best as hints, rather than as reliable tools for
design.
The Java Virtual Machine
specification leaves many details of thread scheduling open to the
implementation. For example, the thread scheduler may or may not
time-slice, that is, interrupt a currently executing thread after
a given amount of time to give another thread a turn. In the JDK
(1.0.2 and 1.1), for example, the Windows NT/95 implementation
provides time-slicing, whereas the Solaris implementation does
not.
The yield method gives
you explicit control over a small piece of the thread-scheduling
picture. By invoking yield, a thread voluntarily ends
its turn at executing, so that the thread scheduler can choose
which thread to run next. (That thread can even be the same one
that just invoked yield, if the scheduler so
chooses.) Using yield judiciously, such as in
computation-intensive loops, enhances turn-taking and fairness
among threads, especially when running on a virtual machine
implementation that does not provide time-slicing. It also helps
your program behave more consistently across platforms.
Moral: Time-slicing and
thread priorities are dangerously variable across virtual machine
implementations; for program correctness, design with
synchronization and yield.
|
|
Q . Does the Java Virtual Machine protect me against deadlocks?
|
Ans :
Yes and no:
a Java Virtual Machine implementation should be designed to avoid
deadlocks within its own code, but it cannot prevent your code
from deadlocking.
A
deadlock occurs when two or more threads each hold a resource that
another is waiting for—neither thread can proceed to the point
where it would free up the resource that the other one is waiting
for. In Java code, the precious resource is usually a monitor lock
needed for a synchronized method or synchronized block. If your
code holds one lock and then tries to acquire another lock, you
run the risk of getting deadlocked.
The prototypical scenario for a
deadlock is the following:
- Thread 1
includes code that acquires the lock for object X (call it
lock X) and, while holding that lock, needs to acquire the
lock for object Y (call it lock Y).
- Thread 2
includes code that acquires lock Y and, while holding that
lock, needs to acquire lock X.
- Thread 1 runs,
acquires lock X, and, before acquiring lock Y, gets put on
hold by the thread scheduler, giving another thread a turn to
run.
- Thread 2 runs,
acquires lock Y, and then tries to acquire lock X. Thread 2
blocks at this point because it can't get the lock—thread 1
is holding it. The thread scheduler tries to find another
runnable thread.
- Thread 1 runs up
to the point it tries to acquire lock Y. Thread 1 then blocks
because it can't get the lock—thread 2 is holding it.
Threads 1 and 2 are now deadlocked. Neither can move forward
because it needs a resource that the -other is holding, and
neither can release the resource it's holding until after it
has moved forward.
The
Java Virtual Machine does not attempt to prevent your program from
deadlocking as it executes—that's simply too hard a problem to
solve in general. A virtual machine implementation, however, can
make it easier to diagnose and pinpoint cases of deadlock after
they've happened. The JDK (1.0.2 and 1.1), for instance, lets you
trigger full dumps of the threads and monitors currently used by
the virtual machine. As your program is running, you can send a
quit signal (ctrl-break on Win32, ctrl-backslash on Solaris) and
get a current picture of the threads and monitors at work in your
virtual machine. Following is an example triggered on Solaris
while running a DeadlockExample class:
Full thread dump:
"Thread-5" (TID:0xe6f009f8, sys_thread_t:0xe6a61de0) prio=5
DeadlockThread.grabSecondLock(DeadlockExample.java:100)
DeadlockThread.grabFirstLock(DeadlockExample.java:95)
DeadlockThread.grabLocks(DeadlockExample.java:88)
DeadlockThread.run(DeadlockExample.java:68)
"Thread-4" (TID:0xe6f00998, sys_thread_t:0xe6a91de0) prio=5
DeadlockThread.grabSecondLock(DeadlockExample.java:100)
DeadlockThread.grabFirstLock(DeadlockExample.java:95)
DeadlockThread.grabLocks(DeadlockExample.java:88)
DeadlockThread.run(DeadlockExample.java:68)
"Finalizer thread" (TID:..., sys_thread_t:...) prio=1
"Async Garbage Collector" (TID:..., sys_thread_t:...) prio=1
"Idle thread" (TID:..., sys_thread_t:...) prio=0 *current thread*
"clock handler" (TID:0xe6f00180, sys_thread_t:0xe6bf1de0) prio=11
"main" (TID:0xe6f00150, sys_thread_t:0x39990) prio=7
DeadlockExample.main(DeadlockExample.java:31)
Monitor Cache Dump:
unknown key (key=0xe6af1de0): unowned
Waiting to be notified:
"Async Garbage Collector"
java.lang.String@...: monitor owner e6a61de0: "Thread-5"
Waiting to enter:
"Thread-4"
java.lang.String@...: monitor owner e6a91de0: "Thread-4"
Waiting to enter:
"Thread-5"
unknown key (key=0x39990): unowned
Waiting to be notified:
"main"
This
thread-and-monitor dump reveals that Thread-4 and Thread-5
are deadlocked. The Full thread dump section shows
that Thread-4 and Thread-5 are each
stuck in their own grabSecondLock method. The Monitor
Cache Dump shows why: Thread-5 owns one lock
(monitor owner) that Thread-4 is waiting for (Waiting
to enter), and Thread-4 owns a lock that Thread-5
is waiting for.
When you suspect your program is
deadlocked, trigger a thread-and-monitor dump and check first for
this telltale waiting-to-enter, monitor-owner pattern. It would be
nice if future implementations automated this check.
|
|
Q . I have several worker threads. I want my main thread to wait for any of them to complete, and take action as soon as any of them completes. I don't know which will complete soonest, so I can't just call Thread.join on that one. How do I do it?
|
Ans :
You need to use the wait/notify mechanism to allow any of the
worker threads to wake up your main thread when the worker has completed.
|
|
|
|