Declaring an immutable or thread-safe object as volatile
ensures that a thread will not observe the object in an inconsistent state prior to its initialization. However, declaring mutable or thread-unsafe objects volatile
does not offer any such advantages. object volatile
to ensure visibility of the most up-to-date object state does not work without the use of explicit synchronization in cases where the object is not thread-safe.
Wiki Markup |
---|
In the absence of synchronization, the effect of declaring an object {{volatile}} is that multiple threads which see the new object reference never see a partially initialized object. However, this holds only when the object is "effectively immutable" \[[Goetz 07|AA. Java References#Goetz 07]\], that is, its state cannot be directly or indirectly changed after the object is initialized or published. |
Noncompliant Code Example
This noncompliant code example declares an instance field of type Date
Map
as volatile
. The problem is that the field can be changed mutated using a setter method setDateputData()
. Even if one thread sees the new reference, the object state that it observes may change in the meantime.
Code Block | ||
---|---|---|
| ||
public class FooContainer<K,V> { public volatile DateMap<K,V> dmap; public FooContainer() { dmap = new DateHashMap<K,V>(); } public voidV setDateget(DateObject datek) { d = datereturn map.get(k); } public Datevoid getDate(putData(K key, V value) { // return dPerform validation of value map.put(key, value); } } public class Client { public void doSomething() { FooContainer fcon = new FooContainer(); // This needs to see the fully constructed object not just the reference if (fcon.dmap != null) { // ... } } } |
The client code depends on the publication of the Date
object to carry out its operations. There is a race condition between setting the date and checking in the client code, whether it is null
. During the race window interval, the state of the Date
object could change.
Compliant Solution
Even if the client thread sees the new reference, the object state that it observes may change in the meantime. Because the object is not effectively immutable, it is unsafe for use in a multi-threaded environment.
Compliant Solution (final)
This compliant solution declares the Map
instance as final
because the semantics of final
dictate that the object will be immediately visible after initializationThe object can only be safely published if it is immutable or thread-safe. Otherwise, explicit synchronization is required to ensure thread safety. This compliant solution uses volatile
to guard retrievals that use the getter method. A synchronized setter method is used to set the value of the Date
object to ensure thread-safety. Defensive copies of the setter argument must be made to make the class Foo
effectively immutable.
Code Block | ||
---|---|---|
| ||
public class FooContainer<K,V> { publicfinal volatile Date dMap<K,V> map; public FooContainer() { dmap = new DateHashMap<K,V>(); } public V } get(Object k) { public synchronized voidreturn setDate(Date date) { // Defensive copyingmap.get(k); } } |
The obvious drawback of this solution is that the setter method cannot be accommodated if the goal is to ensure immutability. The Container
and Map
class are both effectively immutable in this case because the Map
instance can only be manipulated via one of the setters whilst there is none here. The words effectively immutable are used instead of immutable because the Map
instance is technically mutable but restricted here so that it has all the immutability properties. The Map
class does not have any static
methods that can be used to change its state, so the effective immutability property is enforced.
Compliant Solution (volatile)
It follows from the previous compliant solution that it is safe to declare the object volatile
because it is effectively immutable.
Code Block | ||
---|---|---|
| ||
public class Container<K,V> { volatile Map<K,V> map; public Container() { dmap = date.clonenew HashMap<K,V>(); } public DateV getDateget(Object k) { return dmap.cloneget(k); } } |
Compliant Solution (synchronization)
This compliant solution ensures that the Foo
object is immutable by removing the setter method and declaring the Date
field as final
. The Date
object can be safely published as a resultuses explicit synchronization to ensure thread safety. It declares the object volatile
to guard retrievals that use the getter method. A synchronized setter method is used to set the value of the Map
object.
Code Block | ||
---|---|---|
| ||
public class FooContainer<K,V> { publicvolatile final Date dMap<K,V> map; public FooContainer() { dmap = new DateHashMap<K,V>(); } } public V get(Object k) { return map.get(k); } public synchronized Datevoid getDate(putData(K key, V value) { // Perform return d.clone(validation of value map.put(key, value); } } |
This compliant solution has the advantage that it can accommodate the setter method. Declaring the object as volatile
for safe publication using getter methods is cheaper in terms of performance, than declaring the getters as synchronized
. However, it is mandatory to synchronize the setter methods.
Risk Assessment
Failing to synchronize access to shared mutable data can cause different threads to observe different states of the object.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
CON26- J | medium | probable | medium | P12 P8 | L1 L2 |
Automated Detection
TODO
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this rule on the CERT website.
References
Wiki Markup |
---|
\[[Goetz 07|AA. Java References#Goetz 07]\] Pattern #2: "one-time safe publication" |
...