Threads are represented by the Thread
class. The only
way for a user to create a thread is to create an object of this class;
each thread is associated with such an object. A thread will start when
the start()
method is invoked on the corresponding
Thread
object.
The Java programming language provides multiple mechanisms for communicating between threads. The most basic of these methods is synchronization, which is implemented using monitors. Each object in Java is associated with a monitor, which a thread can lock or unlock. Only one thread at a time may hold a lock on a monitor. Any other threads attempting to lock that monitor are blocked until they can obtain a lock on that monitor. A thread t may lock a particular monitor multiple times; each unlock reverses the effect of one lock operation.
The synchronized
statement computes a reference to an object;
it then attempts to perform a lock action on that object's monitor and
does not proceed further until the lock action has successfully completed.
After the lock action has been performed, the body of the
synchronized
statement is executed. If execution of the body
is ever completed, either normally or abruptly, an unlock action is
automatically performed on that same monitor.
A synchronized
method automatically performs a lock action
when it is invoked; its body is not executed until the lock action has
successfully completed. If the method is an instance method, it locks the
monitor associated with the instance for which it was invoked (that is, the
object that will be known as this
during execution of the body
of the method). If the method is static, it locks the monitor associated
with the Class
object that represents the class in which the
method is defined. If execution of the method's body is ever completed,
either normally or abruptly, an unlock action is automatically performed
on that same monitor.
The Java programming language neither prevents nor requires detection of deadlock conditions. Programs where threads hold (directly or indirectly) locks on multiple objects should use conventional techniques for deadlock avoidance, creating higher-level locking primitives that don't deadlock, if necessary.
Other mechanisms, such as reads and writes of volatile variables and
classes provided in the java.util.concurrent
package,
provide alternative ways of synchronization.
synchronized
Statementsynchronized
statement acquires a mutual-exclusion lock
on behalf of the executing thread, executes a block, then releases the
lock. While the executing thread owns the lock, no other thread may
acquire the lock.
synchronized ( Expression ) Block
The type of Expression must be a reference type, or a compile-time error occurs.
A synchronized
statement is executed by first evaluating the
Expression.
If evaluation of the Expression completes abruptly for some
reason, then the synchronized
statement completes abruptly
for the same reason.
Otherwise, if the value of the Expression is null
,
a NullPointerException
is thrown.
Otherwise, let the non-null
value of the
Expression be V. The executing thread locks the
lock associated with V. Then the Block is executed.
If execution of the Block completes normally, then the
lock is unlocked and the synchronized
statement
completes normally. If execution of the Block completes
abruptly for any reason, then the lock is unlocked and the
synchronized
statement then completes abruptly for
the same reason.
Acquiring the lock associated with an object does not of itself
prevent other threads from accessing fields of the object or invoking
unsynchronized methods on the object. Other threads can also use
synchronized
methods or the synchronized
statement in a conventional manner to achieve mutual exclusion.
The locks acquired by synchronized
statements are the
same as the locks that are acquired implicitly by synchronized
methods. A single thread may hold a lock more than once.
The example:
class Test { public static void main(String[] args) { Test t = new Test(); synchronized(t) { synchronized(t) { System.out.println("made it!"); } } } }
prints:
made it!
This example would deadlock if a single thread were not permitted to lock a lock more than once.
A synchronized method acquires a monitor before it executes. For a
class (static) method, the monitor associated with the Class object for
the method's class is used. For an instance method, the monitor associated
with this
(the object for which the method was invoked) is used.
These are the same locks that can be used by the synchronized statement; thus, the code:
class Test { int count; synchronized void bump() { count++; } static int classCount; static synchronized void classBump() {classCount++;} }
has exactly the same effect as:
class BumpTest { int count; void bump() { synchronized (this) { count++; } } static int classCount; static void classBump() { try { synchronized (Class.forName("BumpTest")) { classCount++; } } catch (ClassNotFoundException e) { } } }
The Java programming language allows threads to access shared variables. As a rule, to ensure that shared variables are consistently and reliably updated, a thread should ensure that it has exclusive use of such variables by obtaining a lock that, conventionally, enforces mutual exclusion for those shared variables.
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes.
A field may be declared volatile
, in which case the Java
memory model ensures that all threads see a consistent value for the variable.
If, in the following example, one thread repeatedly calls the method
one
(but no more than Integer.MAX_VALUE
times
in all), and another thread repeatedly calls the method two
:
class Test { static int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
then method two
could occasionally print a value
for j
that is greater than the value of i
,
because the example includes no synchronization and, under the
rules explained in chapter 17, the
shared values of i
and j
might be updated
out of order.
One way to prevent this out-or-order behavior would be to declare
methods one
and two
to be
synchronized
:
class Test { static int i = 0, j = 0; static synchronized void one() { i++; j++; } static synchronized void two() { System.out.println("i=" + i + " j=" + j); } }
This prevents method one
and method two
from
being executed concurrently, and furthermore guarantees that the shared
values of i
and j
are both updated before
method one
returns. Therefore method two
never observes a value for j
greater than that for
i
; indeed, it always observes the same value for
i
and j
.
Another approach would be to declare i
and j
to be volatile
:
class Test { static volatile int i = 0, j = 0; static void one() { i++; j++; } static void two() { System.out.println("i=" + i + " j=" + j); } }
This allows method one
and method two
to be
executed concurrently, but guarantees that accesses to the shared values
for i
and j
occur exactly as many times,
and in exactly the same order, as they appear to occur during execution of
the program text by each thread. Therefore, the shared value for
j
is never greater than that for i
, because
each update to i
must be reflected in the shared value
for i
before the update to j
occurs. It is
possible, however, that any given invocation of method two
might observe a value for j
that is much greater than the
value observed for i
, because method one
might be executed many times between the moment when method
two
fetches the value of i
and the moment
when method two
fetches the value of j
.
Wait actions occur upon invocation of wait()
, or the
timed forms wait(long millisecs)
and wait(long millisecs, int nanosecs)
.
A call of wait(long millisecs)
with a parameter of
zero, or a call of wait(long millisecs, int nanosecs)
with
two zero parameters, is equivalent to an invocation of wait()
.
A thread returns normally from a wait
if it
returns without throwing an InterruptedException.
Let thread t be the thread executing the wait method on object m, and let n be the number of lock actions by t on m that have not been matched by unlock actions. One of the following actions occurs.
IllegalMonitorStateException
is
thrown.IllegalArgumentException
is thrown.InterruptedException
is thrown and t's interruption status is set to false.millisecs
milliseconds plus nanosecs
nanoseconds elapse since the beginning of this wait action.
wait
only within loops that terminate only when some logical condition that the thread is waiting for holds.
Each thread must determine an order over the events that could cause it to be removed from a wait set. That order does not have to be consistent with other orderings, but the thread must behave as though those events occurred in that order.
For example, if a thread t is in the wait set for m, and then both an interrupt of t and a notification of m occur, there must be an order over these events.
If the interrupt is deemed to have occurred first, then t will
eventually return from wait
by throwing
InterruptedException
, and some other thread in the
wait set for m (if any exist at the time of the notification) must
receive the notification. If the notification is deemed to have
occurred first, then t will eventually return normally from
wait
with an interrupt still pending.
InterruptedException
.Notification actions occur upon invocation of methods notify
and notifyAll
. Let thread t be the thread executing
either of these methods on object m, and let n be the
number of lock actions by t on m that have not been matched
by unlock actions. One of the following actions occurs.
IllegalMonitorStateException
is
thrown. This is the case where thread t does not already possess
the lock for target m.notify
action, then, if m's wait set is
not empty, a thread u that is a member of m's current
wait set is selected and removed from the wait set. (There
is no guarantee about which thread in the wait set is selected.)
This removal from the wait set enables u's resumption
in a wait action. Notice however, that u's lock actions
upon resumption cannot succeed until some time after t
fully unlocks the monitor for m.notifyAll
action, then all threads are removed from
m's wait set, and thus resume. Notice however, that only
one of them at a time will lock the monitor required during the
resumption of wait.
Interruption actions occur upon invocation of method
Thread.interrupt
, as well as methods defined to
invoke it in turn, such as ThreadGroup.interrupt
.
Let t be the thread invoking u.interrupt
, for
some thread u, where t and u may be the same.
This action causes u's interruption status to be set to true.
Additionally, if there exists some object m whose wait set
contains u, u is removed from m's wait set.
This enables u to resume in a wait action, in which case this
wait will, after re-locking m's monitor, throw
InterruptedException
.
Invocations of Thread.isInterrupted
can determine a
thread's interruption status. The static method
Thread.interrupted
may be invoked by a thread to
observe and clear its own interruption status.
The above specifications allow us to determine several properties having to do with the interaction of waits, notification and interruption. If a thread is both notified and interrupted while waiting, it may either:
wait
, while still having a
pending interrupt (in other works, a call to Thread.interrupted
would return true)wait
by throwing an
InterruptedException
The thread may not reset its interrupt status and return normally from
the call to wait
.
Similarly, notifications cannot be lost due to interrupts.
Assume that a set s of threads is in the wait set of an
object m, and another thread performs a notify
on m. Then either
wait
, orwait
by
throwing InterruptedException
Note that if a thread is both interrupted and woken via
notify
, and that thread returns from wait
by
throwing an InterruptedException
, then some other thread
in the wait set must be notified.