It is tempting to consider primitive Programmers sometimes assume that composite operations on primitive data are atomic. For instance, you can assume that the codeHowever, this is not true in a multithreaded environment. Even code that does not involve a composite operation can be nonatomic, for instance:
Code Block |
---|
x = 3;
|
is atomic with regard to x. Many people also assume atomicity in operators like the following:
Code Block |
---|
x++;
|
But this is not an atomic operator; it involves reading the current value of x
, adding one to it, and setting x
to the new, incremented value.
Declaring a shared mutable variable volatile
ensures the visibility of the latest updates on it across This code can be fixed by declaring the variable x
as volatile
. However, declaring a shared mutable variable volatile
ensures the visibility of the latest updates to it across other threads but does not guarantee the atomicity of composite operations. For example, the variable increment operation consisting of the sequence read-modify-write is not atomic even when the variable is declared volatile
.
The statement shown below increments the value of x
and is nonatomic because it encapsulates several operations - reading the current value of x
, adding one to it, and setting x
to the new, incremented value.
Code Block |
---|
x++;
|
Wiki Markup |
---|
"Sequential consistency and |
Wiki Markup |
"Sequential consistency and/or freedom from data races still allows errors arising from groups of operations that need to be perceived atomically and are not." \[[JLS 05|AA. Java References#JLS 05]\]. In such cases, the {{java.util.concurrent}} utilities must be used to atomically manipulate a shared variable. If the utilities do not provide the required atomic methods, operations that use the variable should be correctly synchronized. |
...
This rule specifically deals with primitive operators like such as ++
. For atomicity of other functionsa grouping of calls to 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; for more information, nonatomic although this is not an issue with most modern JVMs (see CON25-J. Ensure atomicity when reading and writing 64-bit values).
Noncompliant Code Example
In this noncompliant code example, the field itemsInInventory
can be accessed by multiple threads. However, when a thread is updating the value of itemsInInventory
, it is possible for other threads to read the original value (that is, the value before the update). This is because the post decrement operator is non-atomic.
Code Block | ||
---|---|---|
| ||
private int itemsInInventory = 100;
| ||
Code Block | ||
| ||
private int itemsInInventory = 100;
public int removeItem() {
if(itemsInInventory > 0) {
return itemsInInventory--; // Returns new count of items in inventory
}
return -1; // Error code
}
|
Noncompliant Code Example (volatile
)
This noncompliant code example attempts to fix the race condition by declaring itemsInInventory
as volatile
. This guarantees that updates to the field are immediately visible to any thread that reads the field. However, when a thread However, when a thread is updating the value of itemsInInventory
, it is still possible for other threads to read the original value (that is, the value before the update). This is because the post decrement operator is non-atomic. nonatomic.
Noncompliant Code Example (volatile
)
This noncompliant code example attempts to fix the race condition by declaring itemsInInventory
as volatile
.
Code Block | ||
---|---|---|
| ||
Code Block | ||
| ||
private volatile int itemsInInventory = 100; public int removeItem() { if(itemsInInventory > 0) { return itemsInInventory--; // Returns new count of items in inventory } return -1; // Error code } } |
This guarantees that updates to the field are immediately visible to any thread that reads the field. However, when a thread is updating the value of itemsInInventory
, it is still possible for other threads to read the original value (that is, the value before the update). This is because the post decrement operator is nonatomic even when volatile
is used.
Compliant Solution (java.util.concurrent.atomic
classes)
Volatile variables are unsuitable when more than one load/store operation needs to be atomic. There is an alternative method to perform multiple operations atomically. This compliant solution uses a java.util.concurrent.atomic.AtomicInteger
variable.
...
In this noncompliant code example, the two fields a
and b
may be set by multiple threads, using the setValues()
method. While getSum()
is always guaranteed to return a sum, that sum might be erroneous.
Code Block | ||
---|---|---|
| ||
private volatile int a,; private volatile int b; public int getSum() { return a + b; } public int setValues(int a, int b) { this.a = a; this.b = b; } |
For instance, if a
and b
currently have the value 0, and one thread 1 calls getSum()
while thread 2 another calls setValues(1, 1)
, then getSum() might return 0, 1, or 2. Of these, or 1. The latter case occurs if thread 1 reads a
before thread 2 modifies it, and then thread 1 reads b
after thread 2 has modified itthe value 1 is unacceptable; it is returned when the first thread reads a
and b
after the second thread has set the value of a
but before it has set the value of b
.
Compliant Solution
This compliant solution provides atomicity by method synchronizationsynchronizes the setValues()
method so that the entire operation is atomic.
Code Block | ||
---|---|---|
| ||
private volatile int a,; private volatile int b; public synchronized int getSum() { return a + b; } public synchronized int setValues(int a, int b) { this.a = a; this.b = b; } |
As in Unlike the previous noncompliant code example, if a
and b
currently have the value 0, and one thread 1 calls getSum()
while thread 2 another calls setValues(1, 1)
, then getSum()
may return return 0, or 2, depending on which thread grabs the intrinsic lock first. But the The locking guarantees that getSum()
will not never return the unacceptable value 1.
Risk Assessment
If operations on shared variables are not atomic, unexpected results may be produced. For example, there can be inadvertent information disclosure as one user may be able to receive information about other users.
...