-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathLockFrameworkVsSync.java
More file actions
155 lines (140 loc) · 5.9 KB
/
Copy pathLockFrameworkVsSync.java
File metadata and controls
155 lines (140 loc) · 5.9 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
package Phase7_Concurrency.Multithreading;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock Framework vs Thread Synchronization (`synchronized`)
* ---------------------------------------------------------
* Same goal — guard shared state. Different tradeoffs.
* <p>
*
* This file builds a tiny BANK ACCOUNT three ways:
* <p>
*
* 1. UnsafeAccount - no synchronization at all (race condition).
* 2. SyncAccount - `synchronized` methods.
* 3. LockAccount - explicit ReentrantLock with tryLock.
* <p>
*
* Then it measures and compares the three.
* <p>
*
* Decision Cheatsheet
* -------------------
* synchronized Lock framework
* ------------ --------------
* I need timed acquire yes
* I need to give up if interrupted yes
* I need fair queueing yes
* I need multiple condition variables yes
* I want the simplest code possible yes
* It's a short critical section yes
* I want auto-release on exit yes
* <p>
*
* Modern Practical Rule of Thumb
* ------------------------------
* - Default to `synchronized` when you only need plain mutual
* exclusion.
* - Reach for ReentrantLock the moment you say "I need tryLock with a
* timeout" or "I have two different conditions on the same lock."
* - For pure counters use Atomic* / LongAdder.
* - For "many readers, occasional writer" use ReentrantReadWriteLock or
* StampedLock.
*/
public class LockFrameworkVsSync {
// ---- (1) Unsafe ----
static class UnsafeAccount {
double balance;
void deposit(double a) { balance += a; }
boolean withdraw(double a) {
if (balance >= a) { balance -= a; return true; }
return false;
}
}
// ---- (2) synchronized ----
static class SyncAccount {
double balance;
synchronized void deposit(double a) { balance += a; }
synchronized boolean withdraw(double a) {
if (balance >= a) { balance -= a; return true; }
return false;
}
}
// ---- (3) Lock + tryLock ----
static class LockAccount {
private final ReentrantLock lock = new ReentrantLock();
double balance;
void deposit(double a) {
lock.lock();
try { balance += a; }
finally { lock.unlock(); }
}
boolean withdrawWithTimeout(double a, long ms) throws InterruptedException {
if (!lock.tryLock(ms, TimeUnit.MILLISECONDS)) return false;
try {
if (balance >= a) { balance -= a; return true; }
return false;
} finally { lock.unlock(); }
}
}
public static void main(String[] args) throws InterruptedException {
section("1) Unsafe — race condition");
UnsafeAccount un = new UnsafeAccount();
bench("unsafe", 4, 100_000, () -> un.deposit(1));
System.out.println("unsafe balance = " + un.balance + " (expected 400000)");
section("2) synchronized — correct");
SyncAccount sy = new SyncAccount();
bench("sync ", 4, 100_000, () -> sy.deposit(1));
System.out.println("sync balance = " + sy.balance);
section("3) ReentrantLock — correct + supports tryLock");
LockAccount lk = new LockAccount();
bench("lock ", 4, 100_000, () -> lk.deposit(1));
System.out.println("lock balance = " + lk.balance);
section("4) Lock-only feature: tryLock with timeout");
// Simulate contention then attempt withdraw with a 50ms deadline.
LockAccount busy = new LockAccount();
busy.balance = 1_000;
Thread holder = new Thread(() -> {
busy.deposit(0); // grab the lock briefly
try {
busy.withdrawWithTimeout(0, 200); // hold for ~0s, but the test really
// shouldn't matter — we use a manual
// hand-hold via raw lock for demo:
} catch (InterruptedException ignored) {}
}, "holder");
// Cleaner demonstration: grab lock by hand, sleep, release.
ReentrantLock raw = new ReentrantLock();
Thread squatter = new Thread(() -> {
raw.lock();
try { Thread.sleep(200); } catch (InterruptedException ignored) {}
finally { raw.unlock(); }
}, "squatter");
squatter.start();
Thread.sleep(30);
boolean got = raw.tryLock(50, TimeUnit.MILLISECONDS);
System.out.println("acquire within 50ms? " + got + " (false — squatter held it longer)");
if (got) raw.unlock();
squatter.join();
section("5) When NOT to use either — counter belongs to AtomicLong");
AtomicLong atomic = new AtomicLong();
bench("atomic", 4, 100_000, atomic::incrementAndGet);
System.out.println("atomic = " + atomic.get());
section("done");
}
private static void bench(String label, int threads, int iters, Runnable task) throws InterruptedException {
Thread[] ts = new Thread[threads];
long t0 = System.nanoTime();
for (int i = 0; i < threads; i++) {
ts[i] = new Thread(() -> { for (int k = 0; k < iters; k++) task.run(); });
ts[i].start();
}
for (Thread t : ts) t.join();
long ms = (System.nanoTime() - t0) / 1_000_000;
System.out.println("[" + label + "] " + threads + "x" + iters + " ops in " + ms + " ms");
}
private static void section(String title) {
System.out.println();
System.out.println("=== " + title + " ===");
}
}