A consistent locking policy guarantees that multiple threads cannot simultaneously access or modify shared data. When two or more operations must be performed as a single atomic operation, a consistent locking policy must be implemented using either intrinsic synchronization or java.util.concurrent
utilities. In the absence of such a policy, the code is susceptible to race conditions.
When presented with a set of operations, where each is guaranteed to be atomic, it is tempting to assume that a single operation consisting of individually atomic operations is guaranteed to be collectively atomic without additional locking. Similarly, programmers might incorrectly assume that use of a thread-safe Collection
is sufficient to preserve an invariant that involves the collection's elements without additional synchronization. A thread-safe class can only guarantee atomicity of its individual methods. A grouping of calls to such methods requires additional synchronization for the group.
Consider, for example, a scenario in which the standard thread-safe API lacks a single method both to find a particular person's record in a Hashtable
and to update that person's payroll information. In such cases, the two method invocations must be performed atomically.
Enumerations and iterators also require either explicit synchronization on the collection object (client-side locking) or use of a private final lock object.
Compound operations on shared variables are also non-atomic (see VNA02-J. Ensure that compound operations on shared variables are atomic for more information).
VNA04-J. Ensure that calls to chained methods are atomic describes a specialized case of this rule.
Noncompliant Code Example (AtomicReference
)
This noncompliant code example wraps references to BigInteger
objects within thread-safe AtomicReference
objects:
final class Adder { private final AtomicReference<BigInteger> first; private final AtomicReference<BigInteger> second; public Adder(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()); } }
AtomicReference
is an object reference that can be updated atomically. However, operations that combine more than one atomic reference are non-atomic. In this noncompliant code example, one thread may call update()
while a second thread may call add()
. This might cause the add()
method to add the new value of first
to the old value of second
, yielding an erroneous result.
Compliant Solution (Method Synchronization)
This compliant solution declares the update()
and add()
methods synchronized to guarantee atomicity:
final class Adder { // ... private final AtomicReference<BigInteger> first; private final AtomicReference<BigInteger> second; public Adder(BigInteger f, BigInteger s) { first = new AtomicReference<BigInteger>(f); second = new AtomicReference<BigInteger>(s); } public synchronized void update(BigInteger f, BigInteger s){ first.set(f); second.set(s); } public synchronized BigInteger add() { return first.get().add(second.get()); } }
Noncompliant Code Example (synchronizedList()
)
This noncompliant code example uses a java.util.ArrayList<E>
collection, which is not thread-safe. However, the example uses Collections.synchronizedList
as a synchronization wrapper for the ArrayList
. It subsequently uses an array, rather than an iterator, to iterate over the ArrayList
to avoid a ConcurrentModificationException
.
final class IPHolder { private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>()); public void addAndPrintIPAddresses(InetAddress address) { ips.add(address); InetAddress[] addressCopy = (InetAddress[]) ips.toArray(new InetAddress[0]); // Iterate through array addressCopy ... } }
Individually, the add()
and toArray()
collection methods are atomic. However, when called in succession (as shown in the addAndPrintIPAddresses()
method), there is no guarantee that the combined operation is atomic. The addAndPrintIPAddresses()
method contains a race condition that allows one thread to add to the list and a second thread to race in and modify the list before the first thread completes. Consequently, the addressCopy
array may contain more IP addresses than expected.
Compliant Solution (Synchronized Block)
The race condition can be eliminated by synchronizing on the underlying list's lock. This compliant solution encapsulates all references to the array list within synchronized blocks:
final class IPHolder { private final List<InetAddress> ips = Collections.synchronizedList(new ArrayList<InetAddress>()); public void addAndPrintIPAddresses(InetAddress address) { synchronized (ips) { ips.add(address); InetAddress[] addressCopy = (InetAddress[]) ips.toArray(new InetAddress[0]); // Iterate through array addressCopy ... } } }
This technique is also called client-side locking [Goetz 2006] because the class holds a lock on an object that might be accessible to other classes. Client-side locking is not always an appropriate strategy (see LCK11-J. Avoid client-side locking when using classes that do not commit to their locking strategy for more information).
This code does not violate LCK04-J. Do not synchronize on a collection view if the backing collection is accessible because, although it synchronizes on a collection view (the synchronizedList
result), the backing collection is inaccessible and consequently cannot be modified by any code.
Note that this compliant solution does not actually use the synchronization offered by Collections.synchronizedList()
. If no other code in this solution used it, it could be eliminated.
Noncompliant Code Example (synchronizedMap()
)
This noncompliant code example defines the KeyedCounter
class that is not thread-safe. Although the HashMap
is wrapped in a synchronizedMap()
, the overall increment operation is not atomic [Lee 2009].
final 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 oldValue = (old == null) ? 0 : old.intValue(); if (oldValue == Integer.MAX_VALUE) { throw new ArithmeticException("Out of range"); } map.put( key, oldValue + 1); } public Integer getCount(String key) { return map.get(key); } }
Compliant Solution (Synchronization)
This compliant solution ensures atomicity by using an internal private lock object to synchronize the statements of the increment()
and getCount()
methods:
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 oldValue = (old == null) ? 0 : old.intValue(); if (oldValue == Integer.MAX_VALUE) { throw new ArithmeticException("Out of range"); } map.put(key, oldValue + 1); } } public Integer getCount(String key) { synchronized (lock) { return map.get(key); } } }
This compliant solution avoids using Collections.synchronizedMap()
because locking on the unsynchronized map provides sufficient thread-safety for this application. LCK04-J. Do not synchronize on a collection view if the backing collection is accessible provides more information about synchronizing on synchronizedMap()
objects.
Compliant Solution (ConcurrentHashMap
)
The previous compliant solution is safe for multithreaded use but does not scale because of excessive synchronization, which can lead to decreased performance.
The ConcurrentHashMap
class used in this compliant solution provides several utility methods for performing atomic operations and is often a good choice for algorithms that must scale [Lee 2009].
Note that this compliant solution still requires synchronization, because without it, the test to prevent overflow and the increment will not happen atomically, so two threads calling increment()
can still cause overflow. The synchronization block is smaller and does not include the lookup or addition of new values, so it has less impact on performance than the previous compliant solution.
final class KeyedCounter { private final ConcurrentMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>(); private final Object lock = new Object(); public void increment(String key) { AtomicInteger value = new AtomicInteger(); AtomicInteger old = map.putIfAbsent(key, value); if (old != null) { value = old; } synchronized (lock) { if (value.get() == Integer.MAX_VALUE) { throw new ArithmeticException("Out of range"); } value.incrementAndGet(); // Increment the value atomically } } public Integer getCount(String key) { AtomicInteger value = map.get(key); return (value == null) ? null : value.get(); } // Other accessors ... }
According to Section 5.2.1., "ConcurrentHashMap
," of the work of Goetz and colleagues [Goetz 2006]:
ConcurrentHashMap
, along with the other concurrent collections, further improve on the synchronized collection classes by providing iterators that do not throwConcurrentModificationException
, as a result eliminating the need to lock the collection during iteration. The iterators returned byConcurrentHashMap
are weakly consistent instead of fail-fast. A weakly consistent iterator can tolerate concurrent modification, traverses elements as they existed when the iterator was constructed, and may (but is not guaranteed to) reflect modifications to the collection after the construction of the iterator.
Note that methods such as ConcurrentHashMap.size()
and ConcurrentHashMap.isEmpty()
are allowed to return an approximate result for performance reasons. Code should avoid relying on these return values when exact results are required.
Risk Assessment
Failure to ensure the atomicity of two or more operations that must be performed as a single atomic operation can result in race conditions in multithreaded applications.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
VNA03-J | Low | Probable | Medium | P4 | L3 |
Automated Detection
Some static analysis tools are capable of detecting violations of this rule.
Tool | Version | Checker | Description |
---|---|---|---|
CodeSonar | 8.1p0 | JAVA.CONCURRENCY.VOLATILE | Useless volatile Modifier (Java) |
Coverity | 7.5 | ATOMICITY | Implemented |
Parasoft Jtest | 2024.1 | CERT.VNA03.SSUG CERT.VNA03.MRAV | Make the get method for a field synchronized if the set method is synchronized Access related Atomic variables in a synchronized block |
ThreadSafe | 1.3 | CCE_CC_NON_ATOMIC_GCP | Implemented |
Related Guidelines
CWE-362, Concurrent Execution Using Shared Resource with Improper Synchronization ("Race Condition") |
Bibliography
[API 2014] | |
Section 4.4.1, "Client-side Locking" | |
Section 8.2, Synchronization and Collection Classes | |
[Lee 2009] | Map & Compound Operation |
32 Comments
David Svoboda
I am curious as to the relation of CON07-J and CON01-J. It seems CON07-J pertains to thread-safe code on non-primitive data and CON01-J pertains to non-atomic operations on primitive data. Perhaps these two should be merged?
One note on code samples. I think all NCCEs & CSs should have 'nicknames' next to them (unless there is only one NCCE & one CS). That way we can identify each one easily. For instance, the first 3 NCCE's here, I would nickname synchronizedList, subclass, synchronized methods. And the first two CS's, I would name 'synchronized list' and 'composite collection'. You can use idfferent nicknames if you like, but the code samples here definitely need 'em for identification.
ips
. (This is easy to solve with a getter method). As it is currently defined, the code is secure.Dhruv Mohindra
CON07 and CON01 have been separated even though the central idea is to use synchronization for atomicity. The underlying concept behind this guideline is:
CON01 does not deal with the standard API but thread-unsafety "in the field".
I've specified the nick-names. Thanks.
BigInteger
) and the conclusion was to avoid defining your own API.public
. I need to remind myself time and again not to write secure code in NCEs!doSomething()
outside theCompositeCollection
class would defeat the purpose of composition and the CS. Can you elaborate please? Thanks.David Svoboda
I don't understand the distinction. Maybe I'm just focusing on atomicity. But reading the two rules, I see this one is about atomity for 'non-primitive' API methods, and CON01-J is about guaranteeing atomicity for primitive types and operators, particularly --.
I guess the problem is that this NCCE basically belongs in OBJ05-J. The concurrency aspect adds nothing. There is no text in the NCCE that indicates that concurrency is at all relevant to the OBJ05-J violation.
Bzzzt. Now your NCCE violates OBJ00-J. Declare data members as private and provide accessible wrapper methods. Keep those reminders in mind. You want your NCCEs to abide by every secure coding rule (except the one containing it).
I thought the idea of the CS is that you are adding a new CompositeCollection class which encapsulates access to the
ips
list, and guarantees atomicity. ThedoSomething()
method requires the atomicity & encapsulation, but does not provide any. Furthermore, it is obviously part of some other code (eg theHelper
class in the other code samples.) That's why I think it should go outside the CompositeCollection class. (This is more of a style guideline than a correctness guideline, as the current CS is correct.)Dhruv Mohindra
doSomething()
that is thread-safe and does not exist in the standard thread-safe API for the list class. The current CS uses a composition approach which is different from all approaches discussed so far. I'll try to explain this better in the text if it's not intuitive.David Svoboda
You are referring to the paragraph that discusses the fallacy of assuming a thread-safe collection does not requir eexplicit synchronization. I hold that the same fallacy applies to CON01-J, where the fallacy there is assuming that operators like -- are atomic, when they really aren't.
OK, but then it sounds like it belongs in a rule about concurrency and subclassing, or in a rule by itself.
Agreed. This discussion dovetails into the one at TSM03-J. Do not publish partially initialized objects, which we need not repeat here.
It provides no thread-safety by not employing any thread APIs (synchronized, volatile, etc). And it reads ips only once, which is guaranteed atomic in the CS, since it is synchronized. All the intelligence wrt concurrency is encapsulated in the CompositeCollection class, with the doSomething() method has and needs none. In any real code the doSomething() method will belong to some class, (and should have a better name, too).
Let me make a suggestion. Try changing the name from doSomething() to a name more appropriate for what the code must do...once you know a good name for this snippet of code, the choice of what class it goes into should become obvious. For instance, I would call it printIPAddress(), and with that name, it clearly belongs outside the CompositeCollection class.
Dhruv Mohindra
David Svoboda
ips
object. If you leaveips
private and provide a synchronized getter() method, then other threads can modifyips
directly and cause race conditions. And this remains true even if the methods are syncronized onips
rather than thethis
object. Finally, leavingips
private makes your NCCE compliant with OBJ00-J. This also affects the two subsequent CS's.Dhruv Mohindra
David Svoboda
The public/private nature of
ips
is inconsistent in the NCCEs. It should be private, perhaps with a protected getIps() method for the 2nd NCCE.Last year I made ramblings that this rule should be merged with CON01-J...not sure what the conclusion was. Here's what I surmise. Both rules have good NCCE/CS's, but the titles are not helpful.
Currently I think its all right to leave these rules separate, as long as they xref each other. CON01-J is about atomicitity of primitive fields/references and CON07-J is about atomicity of collections.
Dhruv Mohindra
Simply put, CON01 is about thread-safe API design and CON07 is about using and extending existing thread-safe APIs correctly. The motivation for the latter was to break the misconception "I am using a thread-safe class so whatever operations I will do are atomic as a whole". Intermediate programmers probably know all this, however, they may benefit from the "extending the existing API" part.
David Svoboda
First, I bet intermediate programmers don't know this deep down.
That point aside, I still feel that your distinction applies to both rules. CON01 warns against relying on the 'thread-safety' of primitive types, and CON07 warns against relying on the thread-safety of classes that promise some thread-safety. The misconception is important to quash, but that could be done by just one rule.
I should also mention CON25-J which warns against relying on the thread-safety of 64-bit values.
I'm beginning to think we should move the 2nd NCCE/CS from CON01 to CON07 since it also deals with objects. Then CON07 becomes the general 'composite functions' does not provide atomicity' rule, while CON01 becomes specific warning that ++ and – are really 'composed functions'.
Dhruv Mohindra
We could do this in one rule, but it will then talk about several issues which is more likely to confuse things:
1. Designing thread-safe APIs and pitfalls of volatile. Throw in some sequential consistency too.
2. Extending existing thread-safe classes
3. Quashing the misconception - composite operations on thread-safe classes are atomic
I think CON01 and CON07 could be next to one another in the final order of the guidelines. (hb(CON01, CON07)).
Referring CON25 everywhere is a bit questionable because most of the time, this issue does not manifest. This is not a great yardstick but most programmers don't have to deal with it.
The 2nd NCE/CS from CON01 can be used here because in theory
AtomicReference
is a class that already exists. But according to convention we choose to treat them akin to "atomic variables". I won't say that CON01 is only about primitive types and operations on them. It would be acceptable to have an NCE/CS with object fields in CON01. Note that this still differs from CON07 because we are designing the API as opposed to extending a pre-existing one. Consider for example, the Compliant Solution (thread-safe composition) in CON26. It violates CON07 and not CON01 (see my last comment there).Robert Seacord
My impressions so far is that the distinction between CON01 to CON07 is completely artificial, and that these should probably be combined as one guideline, most likely CON01. BTW, (hb(CON01, CON07)) is already true in the current ordering. 8^)
I don't even agree with David's comment that we should break out increment and decrement from other compound operations. This just reinforces misconceptions that these operations are somehow different (e.g., atomic). I would certainly use these as an example of compound operations.
I'll investigate further, but this rule is highly inaccurate, and trying to maintain a nonsensical distinction between these two guidelines will result in both of them being unusable.
Dhruv Mohindra
In CON01 we largely wanted detectors to catch misuse of volatile for composite operations. CON07 says that independently synchronized calls are not atomic when considered as a group (volatile doesn't come into the picture). The solution happens to be the same. But won't detectors want to treat the violations separately? If we choose to combine them, the introduction from this guideline also ought to go somewhere. My fear was including too much information together in CON01.
Dhruv Mohindra
The real problem was that I accidentally copied the last CS into the previous CS in version 96. David then changed the description of the copied CS because the previous one didn't match. We both failed to realize that the examples in both CSs were the same. I've tried to revert to the original. In the process, I discovered a null ptr exception in the last CS's getCount() method and while fixing it I realized it is no longer atomic. So I am removing the draft-one tag from this guideline until this is sorted out.
Robert Seacord
I still think this paragraph can be clearer:
Maybe we can talk about this later today.
Pennie Walters
The Automated Detection is not complete. It's marked TODO.
Dhruv Mohindra
I like the fact that this sentence was reworded:
to:
Increases readability because the link is pushed back. It might be a good idea to do this where possible.
Dhruv Mohindra
sounds a little odd because arrays and iterators cannot be compared.
"Iterations are performed on an array that contains a copy of the
ArrayList
, to avoid a ConcurrentModificationException."Arraylist should also be Array*L*ist.
"LCK04-J. Do not synchronize on a collection view if the backing collection is accessible provides more information about synchronizing on synchronizedMap objects"
to :
"More information about synchronizing on synchronizedMap objects can be found in LCK04-J. Do not synchronize on a collection view if the backing collection is accessible".
and
"For more information, see INT00-J. Perform explicit range checking to ensure integer operations do not overflow"
Thomas Hawtin
Might want a ConcurrentMap example that uses a cas-loop instead of AtomicInteger:
Also, no need to cast after calling Collection.toArray.
Masaki Kubo
What is "invariant" or "invariant involving multiple objects"? If you could express the same idea without using these term, I think it easier to be understood.
David Svoboda
'Invariant' is a CS technical term...some property that is not made explicit in the code, but is known to be true and the program requires it to be true in order to function. For instance, suppose you use an array of ints to store a bunch of sizes of memory blocks. That array would have the invariant that all values are nonnegative, but that may not ever be explicitly mentioned in the code. Perhaps we should add it to the Definitions section?
Masaki Kubo
Thanks for the clarification, David. Yes, adding the term to the Definitions section definitely helps!
David Svoboda
done
Yozo TODA
I added some words to make explicit the meaning of the sentence
Adding "for the group",
Masaki Kubo
In the following sentence:
It's hard to understand why an invariant on "multiple objects" is significant. Also the connection between multiple objects and individually atomic operations.
David Svoboda
Agreed, I rewrote that sentence. (The 'invariant' was supposed to refer to the atomicity of individual operations.)
Martin Waibel
In the
ConcurrentHashMap
example as it is the overflow test and the increment operation are not done atomically. If two threads pass the test before the first increments while the value equals (Integer.MAX_VALUE - 1), both threads will increment thus causing an undetected overflow.David Svoboda
I've fixed this by adding a synchronized block around the check and increment parts of the code. Sigh. This negates some of the performance improvement of using
ConcurrentHashMap
, but IMHO not all.James Ahlborn
Yes, it would be better to use the CAS support of AtomicInteger:
James Ahlborn
When using a ConcurrentMap and putIfAbsent, if cache hits are expected to outweigh cache misses (which is probably the common case, otherwise, why use a map in the first place?), then this is a better pattern:
Robert Seacord
didn't see this as a resource exhaustion attack so I removed the label.