1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
|
Using double-checked locking on mutable objects in non-volatile fields is not
thread-safe.
If the field is not volatile, the compiler may re-order the code in the
accessor. For more information, see:
* http://jeremymanson.blogspot.com/2008/05/double-checked-locking.html
* http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
* Java Concurrency in Practice, §16.2.4
* [Effective Java 3rd Edition §83][ej3e-83]
The canonical example of *correct* double-checked locking for lazy
initialization is:
```java
class Foo {
/** This foo's bar. Lazily initialized via double-checked locking. */
private volatile Bar bar;
public Bar getBar() {
Bar value = bar;
if (value == null) {
synchronized (this) {
value = bar;
if (value == null) {
bar = value = computeBar();
}
}
}
return value;
}
private Bar computeBar() { ... }
}
```
<!--
TODO: Consider instead:
- moving the synchronized block into a separate method to encourage getBar inlining
- using (sharper) jdk9+ VarHandle.getAcquire together with VarHandle.setRelease
- Suppliers.memoize
- AtomicReference.updateAndGet()
-->
## Alternatives
Double-checked locking should only be used in performance critical classes. For
code that is less performance sensitive, there are simpler, more readable
approaches. Effective Java recommends two alternatives:
### Synchronized Accessors
For lazily initializing instance fields, consider a *synchronized accessor*. In
modern JVMs with efficient uncontended synchronization the performance
difference is often negligible.
```java
// Lazy initialization of instance field - synchronized accessor
private Object field;
synchronized Object get() {
if (field == null) {
field = computeValue();
}
return field;
}
```
### Holder Classes
If the field being initialized is static, consider using the *lazy
initialization holder class* idiom:
```java
// Lazy initialization holder class idiom for static fields
private static class Holder {
static final Object field = computeValue();
}
static Object get() {
return Holder.field;
}
```
## Double-checked locking and immutability
If the object being initialized with double-checked locking is
[immutable](http://jeremymanson.blogspot.com/2008/04/immutability-in-java.html),
then it is safe for the field to be non-volatile. *However*, the use of volatile
is still encouraged because it is almost free on x86 and makes the code more
obviously correct.
Note that immutable has a very specific meaning in this context:
> [An immutable object] is transitively reachable from a final field, has not
> changed since the final field was set, and a reference to the object
> containing the final field did not escape the constructor.
Double-checked locking on non-volatile fields is in general unsafe because the
compiler and JVM can re-order code from the object's constructor to occur
*after* the object is written to the field.
The final modifier prevents that re-ordering from occurring, and guarantees that
all of the object's final fields have been written to before a reference to that
object is published.
[ej3e-83]: https://books.google.com/books?id=BIpDDwAAQBAJ
|