-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathThreadSafety.java
More file actions
178 lines (159 loc) · 6.25 KB
/
Copy pathThreadSafety.java
File metadata and controls
178 lines (159 loc) · 6.25 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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package Phase7_Concurrency.Multithreading;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Thread Safety
* -------------
* A class is THREAD-SAFE when it behaves correctly when used from
* multiple threads, with NO additional synchronization required from
* the caller (Brian Goetz).
* <p>
*
* Strategies for Thread Safety
* ----------------------------
* 1. IMMUTABILITY
* - All fields final, no mutator methods.
* - Once constructed safely, no other thread can change state.
* - Examples: String, Integer, java.time.LocalDate, records.
* <p>
*
* 2. CONFINEMENT
* - Only one thread ever touches the object.
* - Examples: a local variable (stack confinement), ThreadLocal,
* GUI components on the EDT (Swing rule).
* <p>
*
* 3. SYNCHRONIZATION
* - synchronized / Lock guarantees mutual exclusion + visibility.
* - Vector, Hashtable, Collections.synchronizedList(...) are the
* classic synchronized-wrapper approach (compound ops still
* need external synchronization).
* <p>
*
* 4. ATOMIC PRIMITIVES
* - Atomic*, LongAdder, LongAccumulator — lock-free counters.
* <p>
*
* 5. CONCURRENT COLLECTIONS
* - ConcurrentHashMap, CopyOnWriteArrayList, ConcurrentLinkedQueue,
* BlockingQueue.
* <p>
*
* 6. VOLATILE
* - Visibility-only for SINGLE-WRITER fields (e.g. a stop flag).
* <p>
*
* Levels of Thread Safety (Goetz et al.)
* --------------------------------------
* Immutable - safe forever (String, BigDecimal, records).
* Thread-safe - all required synchronization is internal
* (ConcurrentHashMap, AtomicInteger).
* Conditionally safe - safe if caller follows a contract
* (an Iterator over a synchronized list).
* Thread-compatible - the class works in single-threaded code; the
* caller adds synchronization (ArrayList).
* Thread-hostile - cannot be made safe (broken on purpose).
* <p>
*
* Compound operations
* -------------------
* Even a "thread-safe" container can be misused if you compose calls:
* <p>
*
* if (!map.containsKey(k)) map.put(k, v); // race between two calls
* <p>
*
* Use the SINGLE atomic methods instead:
* <p>
*
* map.putIfAbsent(k, v);
* map.computeIfAbsent(k, key -> compute(key));
*/
public class ThreadSafety {
/** A simple non-thread-safe counter. */
static class UnsafeCounter {
int n;
void bump() { n++; }
}
/** Synchronized version. */
static class SyncCounter {
int n;
synchronized void bump() { n++; }
synchronized int get() { return n; }
}
/** Lock-free version using AtomicInteger. */
static class AtomicCounter {
final AtomicInteger n = new AtomicInteger();
void bump() { n.incrementAndGet(); }
int get() { return n.get(); }
}
/** Immutable point — thread-safe by construction. */
static record Point(int x, int y) {
Point translate(int dx, int dy) { return new Point(x + dx, y + dy); }
}
public static void main(String[] args) throws InterruptedException {
section("1) Counter race condition");
UnsafeCounter uc = new UnsafeCounter();
runInParallel(2, 100_000, uc::bump);
System.out.println("unsafe = " + uc.n + " (expected 200000)");
section("2) Fix with synchronized");
SyncCounter sc = new SyncCounter();
runInParallel(2, 100_000, sc::bump);
System.out.println("sync = " + sc.get());
section("3) Fix with AtomicInteger (lock-free)");
AtomicCounter ac = new AtomicCounter();
runInParallel(2, 100_000, ac::bump);
System.out.println("atomic = " + ac.get());
section("4) Immutable: thread-safe by construction");
Point p = new Point(1, 2);
// Any number of threads can read p.x() / p.y() — safe.
Runnable reader = () -> {
for (int i = 0; i < 5; i++) System.out.println(" read " + p);
};
Thread r1 = new Thread(reader);
Thread r2 = new Thread(reader);
r1.start(); r2.start();
r1.join(); r2.join();
section("5) Synchronized-wrapper collections (still need ext. sync for iteration)");
List<Integer> sl = Collections.synchronizedList(new ArrayList<>());
sl.add(1); sl.add(2); sl.add(3);
// Each add/get is atomic. ITERATING is NOT — must wrap externally:
synchronized (sl) {
for (int v : sl) System.out.println(" " + v);
}
section("6) Concurrent containers");
Map<String,Integer> cm = new ConcurrentHashMap<>();
cm.put("a", 1);
// SAFE compound operation:
cm.computeIfAbsent("b", k -> 2);
// UNSAFE compound operation if you wrote:
// if (!cm.containsKey("c")) cm.put("c", 3);
System.out.println("ConcurrentHashMap = " + cm);
List<Integer> snap = new CopyOnWriteArrayList<>(List.of(10, 20, 30));
// Iterating a CoW list snapshots — never throws ConcurrentModificationException.
for (int v : snap) {
snap.add(v + 1); // safe; doesn't affect this iteration
}
System.out.println("CoWArrayList = " + snap);
section("7) Legacy synchronized: Vector and Hashtable");
// Synchronized but slow + their iterators throw ConcurrentModificationException.
Vector<Integer> v = new Vector<>(List.of(1,2,3));
Hashtable<String,Integer> h = new Hashtable<>(Map.of("a",1,"b",2));
System.out.println("vector=" + v + ", hashtable=" + h);
section("done");
}
private static void runInParallel(int threads, int iterations, Runnable task) throws InterruptedException {
Thread[] ts = new Thread[threads];
for (int i = 0; i < threads; i++) {
ts[i] = new Thread(() -> { for (int k = 0; k < iterations; k++) task.run(); });
ts[i].start();
}
for (Thread t : ts) t.join();
}
private static void section(String title) {
System.out.println();
System.out.println("=== " + title + " ===");
}
}