...
Code Block | ||
---|---|---|
| ||
final class Foo { private final ThreadLocal<Foo> perThreadInstance = new ThreadLocal<Foo>(); private Helper helper = null; public Helper getHelper() { if (perThreadInstance.get() == null) { createHelper(); } return helper; } private synchronized void createHelper() { if (helper == null) { helper = new Helper(); } // Any non-null value can be used as an argument to set() perThreadInstance.set(this); } } |
...
Noncompliant Code Example (Immutable)
In this compliant solution, suppose that noncompliant code example, we modify the Helper
class is immutableto be immutable, by declaring its fields final. The Java Memory Model (JMM) guarantees that immutable objects are fully constructed before they become visible to any other thread. Additionally, the The block synchronization in the getHelper()
method suffices to ensure that all methods threads that can see a non-null value of the helper field have a proper happens-before relationship for the update to the helper
reference. This synchronization and the aforementioned JMM guarantee combine to ensure that only fully initialized Helper
objects are visible to threads that see non-null values. Consequently, this compliant solution correctly creates both of the needed happens-before relationshipswill see the fully-initialized Helper
object.
Code Block | ||||
---|---|---|---|---|
| ||||
public final class Helper {
private final int n;
public Helper(int n) {
this.n = n;
}
// Other fields and methods, all fields are final
}
final class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) { // 1st read of helper
synchronized (this) {
if (helper == null) { // 2nd read of helper
helper = new Helper(42);
}
}
}
return helper; // 3rd read of helper
}
}
|
However, this code is still unsafe on some JVM platforms. This occurs because there is no happens-before relationships on the 1st read or the 3rd read of helper
. Therefore, it is possible for the 3rd read to obtain a stale value of null (perhaps because its value was cached or reordered by the compiler). In other words, a call to getHelper() can return null.
Compliant Solution (Immutable)
This compliant solution uses a local variable to reduce the number of reads of the helper
field to 1. Therefore, if the read of helper
yields a non-null value, it is cached in a local variable that is inaccessable to other threads, and safely returned.
Code Block | ||||
---|---|---|---|---|
| ||||
public final class Helper { private final int n; public Helper(int n) { this.n = n; } // Other fields and methods, all fields are final } final class Foo { private Helper helper = null; public Helper getHelper() { private Helper h = helper; // only read of helper if (helperh == null) { synchronized (this) { if (helperh == null) { helperh = new Helper(42); helper = h; } } } return helperh; } } |
Exceptions
LCK10-EX0: Use of the noncompliant form of the double-checked locking idiom is permitted for 32-bit primitive values (for example, int
or float
) [Pugh 2004], although this usage is discouraged. The noncompliant form establishes the necessary happens-before relationship between threads that see an initialized version of the primitive value. The second happens-before relationship (for the initialization of the fields of the referent) is of no practical value because unsynchronized reads and writes of primitive values up to 32-bits are guaranteed to be atomic. Consequently, the noncompliant form establishes the only needed happens-before relationship in this case. Note, however, that the noncompliant form fails for long
or double
because unsynchronized reads or writes of 64-bit primitives lack a guarantee of atomicity and consequently require a second happens-before relationship to guarantee that all threads see only fully assigned 64-bit values (See rule VNA05-J. Ensure atomicity when reading and writing 64-bit values for more information.)
...