Wiki Markup |
---|
Composite operations consisting of more than one discrete operation are, by definition, non-atomic. For example, the Java expression {{x++}} is non-atomic because it is a composite operation consisting of three discrete operations: reading the current value of {{x}}, adding one to it, and writing the new, incremented value back |
x
.Wiki Markup |
---|
"Sequential consistency and/or freedom from data races still allows errors arising from groups of to {{x}}. Errors can arise from composite operations that need to be perceived atomically andbut are not." \[[JLS 05|AA. Java References#JLS 05]\]. In such cases, the {{java.util.concurrent}} utilities can be used to atomically manipulate a shared variable. If these utilities do not provide the required functionality in the form of atomic methods, operations that use the variable should be correctly synchronized. |
Note that, as with volatile
, updated values are immediately visible to other threads when either of these two techniques is used. Synchronization provides a way to safely share object state across multiple threads without the need to reason about reorderings, compiler optimizations and hardware specific behavior.
This rule specifically deals with primitive operators such as ++
. For atomicity of a grouping of calls to independently atomic methods of the existing Java API, see CON07-J. Do not assume that a grouping of calls to independently atomic methods is atomic.
The Java Language Specification also permits reads and writes of 64-bit values to be non-atomic though this is not an issue with most modern JVMs (see CON25-J. Ensure atomicity when reading and writing 64-bit values).
Noncompliant Code Example (post-decrement composite operation)
This noncompliant code example contains a data race that may result in the itemsInInventory
field being incorrectly decremented.
For atomicity of a grouping of calls to independently atomic methods of the existing Java API, see CON07-J. Do not assume that a grouping of calls to independently atomic methods is atomic.
The Java Language Specification also permits reads and writes of 64-bit values to be non-atomic though this is not an issue with most modern JVMs (see CON25-J. Ensure atomicity when reading and writing 64-bit values).
Noncompliant Code Example (post-decrement composite operation)
This noncompliant code example contains a data race that may result in the itemsInInventory
field being incorrectly decremented.
Code Block | ||
---|---|---|
| ||
private int itemsInInventory = 100;
public final int removeItem() {
if (itemsInInventory > 0) {
return itemsInInventory--; // Returns new count of items in inventory
}
return -1; // Error code
} | ||
Code Block | ||
| ||
private int itemsInInventory = 100; public final int removeItemreturnItem() { if (itemsInInventory > 0== Integer.MAX_VALUE) { // Check for integer overflow return itemsInInventory--1; // Returns new count of items in inventoryError Code } else { return -1; // Error code }itemsInInventory++; } } |
For example, if the removeItem()
method is concurrently invoked by two threads, the execution of these threads may be interleaved so that:
...
As a result, a decrement operation is "lost" and the itemsInInventory
value is now incorrect.
Similarly, a the returnItem()
method that increments itemsInInventory
is also nonatomic: non-atomic.
Noncompliant Code Example (volatile
)
This noncompliant code example attempts to resolve the problem by declaring itemsInInventory
as volatile.
Code Block | ||
---|---|---|
| ||
private volatile int itemsInInventory = 100;
| ||
Code Block | ||
| ||
public final int returnItemremoveItem() { if itemsInInventory++; } |
Noncompliant Code Example (volatile
)
This noncompliant code example attempts to resolve the problem by declaring itemsInInventory
as volatile.
Code Block | ||
---|---|---|
| ||
private volatile int itemsInInventory = 100;(itemsInInventory > 0) { return itemsInInventory--; // Returns new count of items in inventory } return -1; // Error code } public final int removeItemreturnItem() { if (itemsInInventory > 0) {== Integer.MAX_VALUE) { // Check for integer overflow return itemsInInventory--1; // ReturnsError newCode count of} itemselse in inventory{ } return -1itemsInInventory++; // Error code } } |
Volatile variables are unsuitable when more than one read/write operation needs to be atomic. The use of a volatile variable in this noncompliant code example guarantees that once itemsInInventory
has been updated, the new value is immediately visible to all threads that read the field. However, because the post decrement operator is nonatomic, even when volatile
is used, the interleaving described in the previous noncompliant code example is still possible.
Similarly, the returnItem()
method does not perform the increment operation atomically.
...
bgColor | #FFcccc |
---|
...
.
Compliant Solution (java.util.concurrent.atomic
classes)
The java.util.concurrent
utilities can be used to atomically manipulate a shared variable. This compliant solution uses a {java.util.concurrent.atomic.AtomicInteger}} variable which allows certain composite operations to be performed atomically.
...
It is necessary to use a local variable temp
because there is a time-of-check to time-of-use (TOCTOU) condition between checking whether the inventory count is less than Integer.MAX_VALUE
and using getAndIncrement()
to increment it. Notably, this functionality could also be implemented by using the compareAndSet()
method. The getAndIncrement()
alternative is useful when control over setting the returned value must lie in the hands of the caller instead of the invoked method (returnItem()
).
...
the invoked method (returnItem()
).
Note that updated shared variables are visible to other threads.
Compliant Solution (method synchronization)
Synchronization provides a way to safely share object state across multiple threads without the need to reason about reorderings, compiler optimizations, and hardware specific behavior.
This compliant solution uses method synchronization to synchronize access to itemsInInventory
. Consequently, access to itemsInInventory
is mutually exclusive and its state consistent across all threads.
Code Block | ||
---|---|---|
| ||
private final int itemsInInventory = 100; public final synchronized int removeItem() { if (itemsInInventory > 0) { return itemsInInventory--; // Returns new count of items in inventory } return -1; // Error Code } |
similarly, the returnItem()
method can be fixed by synchronizing it:
Code Block | ||
---|---|---|
| ||
public final synchronized int returnItem() {
if (itemsInInventory == Integer.MAX_VALUE) { // Check for integer overflow
return -1; // Error Code
} else {
return itemsInInventory++;
}
}
|
Synchronization is more expensive than using the optimized java.util.concurrent
utilities and should only be used when the utilities do not contain the facilities (methods) required to carry out the atomic operation. When synchronizing, care must be taken to avoid deadlocks (see CON12-J. Avoid deadlock by requesting and releasing locks in the same order).
Note that updates to correctly synchronized shared variables are visible to other threads.
Compliant Solution (block synchronization)
...