...
This code can be fixed by declaring the variable x
as volatile
. However, declaring 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 post-increment operation consisting of the sequence read-modify-write is not atomic even when the variable is declared volatile
.
...
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 mustcan be used to atomically manipulate a shared variable. If thethese 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 one 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.
...
The Java Language Specification also permits reads and writes of 64-bit values to be nonatomic although 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)
In this noncompliant code example, the field itemsInInventory
can be accessed by multiple threads.
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
}
|
...
This noncompliant code example attempts to fix the race condition visibility issue by declaring itemsInInventory
as volatile
.
Code Block | ||
---|---|---|
| ||
private volatile int itemsInInventory = 100;
public final 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 once the update has taken place, it is immediately visible to any thread all threads that reads read the field. However, when a thread is in the process of updating the value of itemsInInventory
(after the read and modification, but before the write), 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.
...
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 which allows several composite operations to be performed atomically.
Code Block | ||
---|---|---|
| ||
public class Sync { private final AtomicInteger itemsInInventory = new AtomicInteger(100); private final private int removeItem() { for (;;) { int old = itemsInInventory.get(); if (old > 0) { int next = old - 1; // Decrement if (itemsInInventory.compareAndSet(old, next)) { return next; //returns Returns new count of items in inventory } } else { return -1; // Error code } } } } |
Wiki Markup |
---|
According to the Java API \[[API 06|AA. Java References#API 06]\], class {{AtomicInteger}} documentation: |
Wiki Markup \[AtomicInteger is an\] An {{int}} value that may be updated atomically. An {{AtomicInteger}} is used in applications such as atomically incremented counters, and cannot be used as a replacement for an {{Integer}}. However, this class does extend {{Number}} to allow uniform access by tools and utilities that deal with numerically-based classes.
Wiki Markup |
---|
The {{compareAndSet()}} method takes two arguments, the expected value of a variable when the method is invoked and the updated value. This compliant solution uses this method to atomically set the value of {{itemsInInventory}} to the given updated value if and only if the current value equals the expected value. \[[API 06|AA. Java References#API 06]\] |
...
This compliant solution uses method synchronization to synchronize access to shared variables itemsInInventory
. Consequently, access to itemsInInventory
is mutually exclusive and its state consistent across all object statesthreads.
Code Block | ||
---|---|---|
| ||
private final int itemsInInventory = 100;
public synchronized int removeItem() {
if (itemsInInventory > 0) {
return itemsInInventory--; // Returns new count of items in inventory
}
return -1; // Error Code
}
|
Synchronization is more expensive than using the optimized {{java Wiki Markup 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 using explicit synchronization, the programmer must also ensure that two or more threads are not mutually accessible from a different set of two or more threads such that each thread holds a lock while trying to obtain another lock that is held by the other thread \[[Lea 00|AA. Java References#Lea 00]\]. Failure to follow this advice results in deadlocks ([CON12-J. Avoid deadlock by requesting and releasing locks in the same order]When synchronizing, care must be taken to avoid deadlocks (see CON12-J. Avoid deadlock by requesting and releasing locks in the same order).
Compliant Solution (block synchronization)
...
Code Block | ||
---|---|---|
| ||
private final int itemsInInventory = 100;
public int removeItem() {
synchronized(this) {
if (itemsInInventory > 0) {
return itemsInInventory--; // Returns new count of items in inventory
}
return -1; // Error code
}
}
|
Block synchronization is preferable over method synchronization because it reduces the duration for which the lock is held and also protects against denial of service attacks, though the latter . Block synchronization requires synchronizing on a an internal private
lock object instead of the intrinsic lock of the class's object (see CON04-J. Use the private lock object idiom instead of the this
reference. object's intrinsic locking mechanism).
When the number of items is 0 most of the time, If the synchronized
block was may be moved inside the if
condition to reduce the performance cost associated with synchronization. In that case, the variable itemsInInventory
would must be required to be declared as volatile
because the check to determine whether it is greater than 0 relies should rely on the latest value of the variable itemsInInventory
.
Compliant Solution (ReentrantLock
)
This compliant solution uses a java.util.concurrent.locks.ReentrantLock
to atomically perform the post-decrement operation.
Code Block | ||
---|---|---|
| ||
public class Sync { private int itemsInInventory = 100; private final Lock lock = new ReentrantLock(); public int removeItem() { Boolean myLock = false; try { myLock = lock.tryLock(); if (itemsInInventory > 0) { return itemsInInventory--; } } finally { if (myLock) { lock.unlock(); } } return -1; // Error code } } |
Code that uses this lock behaves similar to synchronized code that uses the traditional monitor lock. In addition, it ReentrantLock
provides several other capabilities, for instance, the tryLock()
method does not block waiting if another thread is already holding the lock. The class java.util.concurrent.locks.ReentrantReadWriteLock
can be used when a some thread requires a lock to write information while other threads require the lock to simultaneously read the information.
...
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; } |
The getSum()
method may return a different sum every time it is invoked from different threads. For instance, if a
and b
currently have the value 0, and one thread calls getSum()
while another calls setValues(1, 1)
, then getSum()
might return 0, 1, or 2. Of these, the 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
.
...
Unlike the noncompliant code example, if a
and b
currently have the value 0, and one thread calls getSum()
while another calls setValues(1, 1)
, getSum()
may return return 0, or 2, depending on which thread grabs obtains the intrinsic lock first. The locking guarantees that getSum()
will never return the unacceptable value 1.
...