File: DoubleCheckedLocking.md

package info (click to toggle)
error-prone-java 2.18.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 23,204 kB
  • sloc: java: 222,992; xml: 1,319; sh: 25; makefile: 7
file content (105 lines) | stat: -rw-r--r-- 3,232 bytes parent folder | download
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