Thread-safety guarantees that no two threads can simultaneously access or modify some shared data. However, if two or more operations need to be performed as a single large atomic operation, it becomes necessary to add additional locking in order to enforce atomicity. It is possible for two threads to read some shared value, independently perform operations on it and induce a race condition while storing the final result.
If In presence of an invariant exists involving two objects, it is tempting to believe that if operations on the two objects are individually atomic, no additional locking is required; however this is not the case. Likewise, programmers sometimes assume that using a thread-safe Collection
does not require explicit synchronization in order to preserve an invariant involving the collection's elements. Any object that guarantees atomicity A thread-safe class can only guarantee atomicity over the of individual methods it promises to be thread-safe. A grouping of calls to such methods requires additional synchronization.
For example, consider a scenario where the standard thread-safe API may does not provide a method to both find a particular person's record in a Hashtable
and also update the corresponding payroll information. In such cases, a custom atomic method must be designed and used. This guideline shows the need of such a method to perform a group of independently atomic operations as one atomic operation, and also why such a method is necessary and suggests techniques for incorporating the method it using a custom API.
This guideline applies to all uses of Collection
classes including the thread-safe Hashtable
class. Enumerations of the objects of a Collection
and iterators also require explicit synchronization on the Collection
object (client-side locking) or any single internal private lock object.
Some primitive operators may also not be atomic; see Expressions involving compound operators are also non-atomic. Refer to CON01-J. Ensure that compound operations on shared variables are atomic for more information.
...
This noncompliant code example uses two AtomicReference
objects to that hold one BigInteger
object reference, each.
Code Block | ||
---|---|---|
| ||
final class AtomicAdder {
private final AtomicReference<BigInteger> first;
private final AtomicReference<BigInteger> second;
public AtomicAdder(BigInteger f, BigInteger s) {
first = new AtomicReference<BigInteger>(f);
second = new AtomicReference<BigInteger>(s);
}
public void update(BigInteger f, BigInteger s) { // Unsafe
first.set(f);
second.set(s);
}
public BigInteger add() { // Unsafe
return first.get().add(second.get());
}
}
|
An AtomicReference
is an object reference that can be updated atomically. Operations that use these two atomic references independently are guaranteed to be atomic, however, if an operation involves using both together, thread-safety issues arise. For instance, in In this noncompliant code example, one thread could may call update()
while a second thread calls may call add()
, with the result that . This might cause the add()
operation adds to add the newer new value of first
to the older old value of second
, yielding an erroneous result.
...
Code Block | ||
---|---|---|
| ||
class AtomicAdder { // ... public synchronized void update(BigInteger f, BigInteger s){ first.set(f); second.set(s); } public synchronized BigInteger add() { return first.get().add(second.get()); } } |
Prefer using the block form of synchronization when there are nonatomic operations within the method contains non-atomic operations that either do not require any synchronization . These or can use a more fine-grained locking scheme involving multiple internal private lock objects. Non-atomic operations can be decoupled from those that require synchronization and executed outside the synchronized block. You should also consider using an internal private lock object, as recommended by . The guideline CON04-J. Synchronize using an internal private final lock object has more details on using private internal lock objects and block synchronization.
Noncompliant Code Example (synchronizedList)
This noncompliant code example comprises an ArrayList
java.util.ArrayList<E>
collection which is non-not thread-safe by default. However, most thread-unsafe classes have a synchronized thread-safe version, for example, Collections.synchronizedList
is a good substitute for ArrayList
and Collections.synchronizedMap
is a good alternative to HashMap
. The atomicity pitfall described in the subsequent paragraph, remains to be addressed even when the particular Collection
offers thread-safety benefitsguarantees.
Code Block | ||
---|---|---|
| ||
final class RaceCollectionIPHolder { private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>()); public void addIPAddress(InetAddress ia) { // Validate ia ips.add(ia); } public void addAndPrintIP() throws UnknownHostException { addIPAddress(InetAddress.getLocalHost()); InetAddress[] ia = (InetAddress[]) ips.toArray(new InetAddress[0]); System.out.println("Number of IPs: " + ia.length); } } |
When the addAndPrintIP()
method is invoked on the same object from multiple threads, the output, consisting of varying array lengths, may indicate a race condition between the threads. In other words, the statements in method addAndPrintaddAndPrintIP()
that are responsible for adding an IP address and printing it out the length of the list, are not sequentially consistent.
...
To eliminate the race condition, ensure atomicity by using the underlying list's lock. This can be achieved by including all statements that use the array list within a synchronized block that locks on the list.
Code Block | ||
---|---|---|
| ||
final class RaceCollectionIPHolder { private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>()); public void addIPAddress(InetAddress ia) { synchronized (ips) { // Validate ips.add(ia); } } public void addAndPrintIP() throws UnknownHostException { synchronized (ips) { addIPAddress(InetAddress.getLocalHost()); InetAddress[] ia = (InetAddress[]) ips.toArray(new InetAddress[0]); System.out.println("Number of IPs: " + ia.length); } } } |
...
Wiki Markup |
---|
Although expensive, {{CopyOnWriteArrayList}} and {{CopyOnWriteArraySet}} classes are sometimes used to create copies of the core {{Collection}} so that iterators do not fail with a runtime exception when some data in the {{Collection}} is modified. However, any updates to the {{Collection}} are not immediately visible to other threads. Consequently, theirthe use of these classes is limited to boosting performance in code where the writes are fewer (or non-existent) as compared to the reads \[[JavaThreads 04|AA. Java References#JavaThreads 04]\]. In allmost other cases they must be avoided (see [MSC13-J. Do not modify the underlying collection when an iteration is in progress] for details on using these classes). |
Noncompliant Code Example (synchronizedMap
)
Wiki Markup |
---|
This noncompliant code example defines a thread-unsafeclass {{KeyedCounter}} class which is not thread-safe. Even though the {{HashMap}} field is synchronized, the overall {{increment}} operation is not atomic. \[[Lee 09|AA. Java References#Lee 09]\] |
Code Block | ||
---|---|---|
| ||
publicfinal class KeyedCounter { private final Map<String, Integer> map = Collections.synchronizedMap(new HashMap<String, Integer>()); public void increment(String key) { Integer old = map.get(key); int value = (old == null) ? 1 : old.intValue() + 1; map.put(key, value); } public Integer getCount(String key) { return map.get(key); } } |
...
This compliant solution uses a private internal lock object lock to synchronize the method bodies of the increment()
and getCount
methods, to ensure atomicity. For more information on private object locks, see CON04-J. Synchronize using an internal private final lock objectatomicity.
Code Block | ||
---|---|---|
| ||
public final class KeyedCounter { private final Map<String,Integer> map = new HashMap<String, Integer>(); private final Object lock = new Object(); public void increment(String key) { synchronized (lock) { Integer old = map.get(key); int value = (old == null) ? 1 : old.intValue() + 1; map.put(key, value); } } public Integer getCount(String key) { synchronized (lock) { return map.get(key); } } } |
Note that this solution does not use Collections.synchronizedMap()
. This is because locking on the (unsynchronized) map provides sufficient thread-safety for this application. The rule guideline CON02-J. Always synchronize on the appropriate object indicates enlists certain objects that are should not to be synchronized on, and this includes be used for synchronization purposes, including any object returned by Collections.synchronizedMap()
.
...
Wiki Markup |
---|
The previous compliant solution does not scale very well because a class with several {{synchronized}} methods can be a potential bottleneck as far as acquiring locks is concerned and may further leadyield toa contentiondeadlock or deadlocklivelock. The class {{ConcurrentHashMap}}, through a more preferable approach, provides several utility methods to perform atomic operations and is used often a good choice, as demonstrated in this compliant solution \[[Lee 09|AA. Java References#Lee 09]\]. |
...