-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathJavaMemoryModel.java
More file actions
158 lines (143 loc) · 6.15 KB
/
Copy pathJavaMemoryModel.java
File metadata and controls
158 lines (143 loc) · 6.15 KB
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package Phase7_Concurrency.Multithreading;
/**
* Java Memory Model (JMM)
* -----------------------
* The JMM is the formal specification of how reads and writes from
* different threads interact. Without it, a program is at the mercy of
* the JIT compiler, the CPU's memory subsystem, and the OS scheduler —
* any of which may REORDER or CACHE memory operations.
* <p>
*
* The Two Concerns
* ----------------
* ATOMICITY - is the read/write a single indivisible step?
* (long and double WITHOUT volatile are NOT guaranteed
* to be atomic on 32-bit platforms.)
* VISIBILITY - when does a thread see another thread's write?
* (Without synchronization, possibly never.)
* <p>
*
* Happens-Before
* --------------
* The JMM defines a partial order called HAPPENS-BEFORE. If action A
* happens-before action B, then A's effects are visible to B.
* <p>
*
* The standard happens-before edges:
* 1. Program order within a single thread.
* 2. Monitor unlock → subsequent lock of the SAME monitor.
* 3. volatile write → subsequent volatile read of the SAME field.
* 4. Thread.start() → first action of the started thread.
* 5. Last action of a thread → Thread.join() returning in another.
* 6. Default initialization (the JLS-defined zero-values) →
* any subsequent action.
* 7. Transitively: A→B and B→C implies A→C.
* <p>
*
* If there is NO happens-before edge between two actions on different
* threads, the JVM is allowed to reorder / cache them. Bugs that result:
* <p>
*
* - The reader sees stale data forever (e.g., never-ending loop on
* a non-volatile flag).
* - The reader sees the new value of one field but the OLD value of
* another, even if the writer wrote them in order.
* - Constructor-leak: another thread sees a partially-initialised
* object via an unsafe publication.
* <p>
*
* Safe Publication
* ----------------
* Common idioms that publish an object safely:
* - Initialize from a static initializer.
* - Store into a `final` field of a properly constructed object.
* - Store into a `volatile` field.
* - Store into a field protected by a lock.
* - Use a thread-safe container (ConcurrentHashMap etc.).
* <p>
*
* `final` Fields — Special Rule
* -----------------------------
* Final fields, once a constructor returns, are guaranteed visible to
* any thread that gets a reference to the object — even without
* synchronization. This is why immutable objects "just work."
* <p>
*
* Tools in this file
* ------------------
* 1. Visibility bug — non-volatile stop flag never sees the update.
* 2. Fix with volatile.
* 3. Long write tearing (in practice, hard to demonstrate on 64-bit).
* 4. Reordering — write-then-write can appear to a reader in either order.
* 5. final field safe-publication.
*/
public class JavaMemoryModel {
/** Non-volatile flag. The reader may never see writes to it. */
static boolean stopNonVolatile = false;
/** Volatile flag. The reader is GUARANTEED to see writes. */
static volatile boolean stopVolatile = false;
/** Non-volatile longs left for reordering demo. */
static long a, b;
public static void main(String[] args) throws InterruptedException {
section("1) Visibility — without volatile, reader may NEVER stop");
// This demo is racy by design. Some JVMs / JITs WILL show the
// hang; some won't. Either way, the program is wrong.
Thread eagerReader = new Thread(() -> {
long n = 0;
while (!stopNonVolatile) n++;
System.out.println("reader exited after " + n + " loops (you got lucky)");
}, "no-volatile");
eagerReader.setDaemon(true); // so the JVM doesn't hang if it spins
eagerReader.start();
Thread.sleep(50);
stopNonVolatile = true;
// give the reader a tiny grace period; if it's still alive, we abandon it.
eagerReader.join(500);
if (eagerReader.isAlive()) {
System.out.println("reader is STILL spinning — visibility bug demonstrated");
}
section("2) Visibility fix — volatile flag is seen immediately");
Thread goodReader = new Thread(() -> {
long n = 0;
while (!stopVolatile) n++;
System.out.println("goodReader exited after " + n + " loops");
}, "volatile");
goodReader.start();
Thread.sleep(50);
stopVolatile = true;
goodReader.join();
section("3) happens-before via Thread.join");
// join guarantees the calling thread sees everything the joined
// thread did. No volatile, no synchronized needed for `result`.
int[] result = new int[1];
Thread writer = new Thread(() -> result[0] = 99, "writer");
writer.start();
writer.join(); // happens-before edge
System.out.println("read after join = " + result[0]);
section("4) Reordering — writes may not appear in source order to other threads");
// This is famously hard to observe consistently, but the JMM
// permits it. Use volatile or synchronized to forbid.
a = 0; b = 0;
Thread W = new Thread(() -> { a = 1; b = 2; });
// a reader may see (a=0,b=2) — i.e., the second write before the first.
// (No assertion here — just narration. See VolatileKeyword.java for
// a concrete demonstration with the publication idiom.)
W.start(); W.join();
System.out.println("after join: a=" + a + ", b=" + b);
section("5) Safe publication via `final`");
Immutable im = new Immutable(7, 9);
Thread t = new Thread(() -> System.out.println("sees " + im.x + ", " + im.y));
t.start(); t.join();
section("done");
}
/** Object with final fields — safe to share without further synchronization. */
static class Immutable {
final int x;
final int y;
Immutable(int x, int y) { this.x = x; this.y = y; }
}
private static void section(String title) {
System.out.println();
System.out.println("=== " + title + " ===");
}
}