...
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 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 post increment composite operation in the returnItem()
method is non-atomic.
...
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.
Code Block | ||
---|---|---|
| ||
private final AtomicInteger itemsInInventory = new AtomicInteger(100); private final int removeItem() { for (;;) { int old = itemsInInventory.get(); if (old > 0) { int next = old - 1; // Decrement if (itemsInInventory.compareAndSet(old, next)) { return next; // Returns new count of items in inventory } } else { return -1; // Error code } } } |
Note that updates to shared variables become instantly visible to other threads when this approach is used.
Wiki Markup |
---|
According to the Java API \[[API 06|AA. Java References#API 06]\], class {{AtomicInteger}} documentation: |
...
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 updated value if and only if the current value equals the expected value \[[API 06|AA. Java References#API 06]\]. The {{for}} loop guarantees the same behavior of the original function, namely that the function succeeds in decrementing {{itemsInInventory}} or an error code is returned. |
The returnItem()
method can be fixed by using the java.util.concurrent.atomic.AtomicInteger.getAndIncrement()
method.
...
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()
).
Note that updated shared variables are visible to other threads.
Compliant Solution (method synchronization)
...
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 } public synchronized final int returnItem() { if (itemsInInventory == Integer.MIN_VALUE) { // Check for integer overflow return -1; } return itemsInInventory++; } |
If code is synchronized correctly, updates to shared variables are instantly made visible to other threads. Synchronization is more expensive than using the optimized java.util.concurrent
utilities and should only generally be used when the utilities do not contain the facilities (methods) required to carry out the atomic operationpreferred when it is sufficiently complex to carry out the operation atomically using the utilities. 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)
...
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 } } |
similarly, the returnItem()
method can be fixed by using block synchronization:
Code Block | ||
---|---|---|
| ||
public final int returnItem() { synchronized(this) { if (itemsInInventory == Integer.MIN_VALUE) { // Check for integer overflow return -1; } return itemsInInventory++; } } |
Similarly, the returnItem()
method can be fixed by using block synchronization.
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. Block synchronization requires synchronizing on 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 Class object's intrinsic locking mechanism).
...