- 221 -
10.6 Atomic Access and Assignment
Variables shared between multiple threads e.g., instance variables of objects have atomic assignment guaranteed by the Java language specification for all data types except for
long
s and
double
s. Actually, the storing of a value into a variable takes two primitive operations, a store and a write. However, the language specification also states that once a store operation occurs on a
particular variable, no other store operation is allowed on that variable until the write operation has occurred. The specification allows
long
s and
double
s to be stored in two separate sets of store+write operations, hence their exception to atomicity. A similar atomic specification applies
for reading variables.
This means that access and update of variables are automatically synchronized as long as they are not
long
s or
double
s. If a method consists solely of a variable access or assignment, there is no need to make it
synchronized
for thread safety, and every reason not to do so for performance. Thread safety extends further to any set of statements that are accessing or assigning to a variable
independently of any other variable values. The exclusion here precludes setting a variable that depends on the value of another variable as being thread-safe; this would be two separate
operations, which is inherently not thread-safe. For example:
public void setMeObject o {me = o;} public Object getMe {return me;}
are thread-safe methods, with no need for
synchronized
modifiers to be added to the method declaration. On the other hand:
public void setMeObject o {ifoverwrite me = o;}
is not thread-safe:
overwrite
may be true at the time of checking in the
if
statement, but false by the time of the subsequent assignment statement. Anything more complex than simple assignments
and accesses is probably not thread-safe: it depends on whether any particular intermediate state that can be accessed is considered corrupt by the application. Consider the code being halted before
or after any particular atomic statement, and decide whether or not another thread could now access a corrupt application state.
Combining several calls to methods that atomically assign variables is the same problem as combining several calls to synchronized methods. The individual calls are executed atomically, but
the combination is not necessarily atomic:
public void setMe1Object o {me = o;} public void setMe2Object o {me = o;}
public void setBothObject o1, Object o2 {setMe1o1;setMe2o2;}
For these three methods, it does not matter whether
setMe1
and
setMe2
are synchronized or not.
setBoth
is not synchronized, so it can be interrupted between the calls to
setMe1
and
setMe2
, allowing another thread to update one of the instance variables. This can leave the object in a potentially corrupt application state if both instance variables are always supposed to be
updated together. Specifically, if two threads call the
setBoth
method simultaneously, the outcome is not predictable unless
setBoth
is synchronized.
Synchronization Ordering
It is easy to confuse exactly what synchronization does. Synchronization ensures that a set
- 222 - of statements executes exclusively for a particular monitor. Synchronization does not
guarantee the order of execution of synchronized blocks. If two threads try to execute a synchronized block simultaneously, one succeeds first, but there is no guarantee as to
which one that is.
Atomic assignment is the case where the set of synchronized statements is one statement, and the synchronization is set by the VM. When considering atomic assignment, you
might ask the question, What if a context switch occurs during the method call setup or tear down? When does the synchronization happen, and what happens with the context
switch? The actual moment when the synchronization occurs does not matter. It does not matter if a context switch happens at any time before or after a set of synchronized
statements. Either the synchronized set has not been entered, or it has been completed. Only the actual granting of the lock matters, and that is atomic with respect to all
interested threads.
Until you reach an atomic assignment statement, it makes no difference whether another atomic assignment on the same variable occurs. This is purely the ordering of
assignments, which is not guaranteed with synchronization anyway. After the atomic assignment is finished, it is complete. A context switch hitting the method tear down does
not matter. The only reason to synchronize a simple updator is to avoid a corrupt assignment, i.e., two threads simultaneously updating the same variable, and the resulting
value being neither of the updated values. This can indeed occur for
double
s and
long
s, but not for other data types.
For serious number crunching involving
double
s and
long
s, I recommend using separate data structures for each thread or using a VM that guarantees atomic assignment for
double
s and
long
s. A longer discussion about Javas atomicity can be found in an article by Art Jolin,
[6]
where he discusses unsynchronized thread-safe data structures, including why a binary tree specifically the
AWTEventMulticaster
class can be thread-safe without any
synchronized
methods.
[6]
Javas Atomic Assignment, Java Report, August 1998.
10.7 Thread Pools