Java Reference
In-Depth Information
cases, the program's behavior cannot be trusted—sometimes the program will produce the
correct results, and sometimes it won't, and there won't be any indication that the shared
object was manipulated incorrectly.
The problem can be solved by giving only one thread at a time
exclusive access
to code
that accesses the shared object. During that time, other threads desiring to access the object
are kept waiting. When the thread with exclusive access finishes accessing the object, one of
the waiting threads is allowed to proceed. This process, called
thread synchronization
, coor-
dinates access to shared data by multiple concurrent threads. By synchronizing threads in this
manner, you can ensure that each thread accessing a shared object
excludes
all other threads
from doing so simultaneously—this is called
mutual exclusion
.
Actually, thread synchronization is necessary
only
for shared
mutable
data
, i.e., data that
may
change
during its lifetime. With shared
immutable data
that will
not
change, it's not
possible for a thread to see old or incorrect values as a result of another thread's manipu-
lation of that data.
When you share
immutable data
across threads, declare the corresponding data fields
final
to indicate that the values of the variables will
not
change after they're initialized.
This prevents accidental modification of the shared data, which could compromise thread
safety.
Labeling object references as
final
indicates that the reference will not change, but it
does not guarantee that the referenced object is immutable—this depends entirely on the object's
properties.
However, it's still good practice to mark references that will not change as
final
.
Software Engineering Observation 23.3
Always declare data fields that you do not expect to change as
final
. Primitive variables that
are declared as
final
can safely be shared across threads. An object reference that's declared
as
final
ensures that the object it refers to will be fully constructed and initialized before it's
used by the program, and prevents the reference from pointing to another object.
A common way to perform synchronization is to use Java's built-in
monitors
. Every object
has a monitor and a
monitor lock
(or
intrinsic lock
). The monitor ensures that its object's
monitor lock is held by a maximum of only one thread at any time. Monitors and monitor
locks can thus be used to enforce mutual exclusion. If an operation requires the executing
thread to
hold a lock
while the operation is performed, a thread must
acquire the lock
before
proceeding with the operation. Other threads attempting to perform an operation that re-
quires the same lock will be
blocked
until the first thread
releases the lock
, at which point
the
blocked
threads may attempt to acquire the lock and proceed with the operation.
To specify that a thread must hold a monitor lock to execute a block of code, the code
should be placed in a
synchronized
statement
. Such code is said to be
guarded
by the
monitor lock; a thread must
acquire the lock
to execute the guarded statements. The mon-
itor allows only one thread at a time to execute statements within
synchronized
state-
ments that lock on the same object, as only one thread at a time can hold the monitor lock.
The
synchronized
statements are declared using the
synchronized
keyword
: