Skip to content

Commit c806224

Browse files
committed
Concurrency: atomic, concurrent_collections, executors
1 parent f4c6ece commit c806224

File tree

7 files changed

+1997
-5
lines changed

7 files changed

+1997
-5
lines changed

TOPICS_PLAN.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,21 @@ src/main/java/org/nkcoder/
3737
|--------------|--------|-------------------------------|------------------------------------------|
3838
| preview/ | [x] | StructuredConcurrencyExample | StructuredTaskScope, Joiner (5th preview)|
3939

40+
**Interview Priority (Completed):**
41+
42+
| Sub-package | Status | Examples | Concepts |
43+
|-------------------------|--------|---------------------------------------------------------------|--------------------------------------------|
44+
| executors/ | [x] | ExecutorServiceExample, CompletableFutureExample | ExecutorService, async programming |
45+
| atomic/ | [x] | AtomicIntegerExample, AtomicReferenceExample | Atomic classes, CAS operations |
46+
| concurrent_collections/ | [x] | ConcurrentHashMapExample, CopyOnWriteListExample | Thread-safe collections |
47+
4048
**Foundational (To Add Later):**
4149

4250
| Sub-package | Status | Examples | Concepts |
4351
|-------------------------|--------|---------------------------------------------------------------|--------------------------------------------|
4452
| thread/ | [ ] | ThreadExample, DaemonThreadExample | Thread creation, lifecycle, daemon threads |
4553
| synchronization/ | [ ] | SynchronizedExample, VolatileExample, WaitNotifyExample | synchronized, volatile, wait/notify |
4654
| locks/ | [ ] | ReentrantLockDemo, ReadWriteLockExample | ReentrantLock, ReadWriteLock |
47-
| atomic/ | [ ] | AtomicIntegerExample, AtomicReferenceExample | Atomic classes, CAS operations |
48-
| executors/ | [ ] | ExecutorServiceExample, CompletableFutureExample | ExecutorService, async programming |
49-
| concurrent_collections/ | [ ] | ConcurrentHashMapExample, CopyOnWriteListExample | Thread-safe collections |
5055
| utilities/ | [ ] | CountDownLatchExample, SemaphoreExample | Synchronization utilities |
5156

5257
---
@@ -163,7 +168,7 @@ src/main/java/org/nkcoder/
163168

164169
| Topic | Status | Progress |
165170
|--------------|-------------|----------|
166-
| concurrency | In Progress | 3/10 |
171+
| concurrency | In Progress | 9/18 |
167172
| collections | Complete | 6/6 |
168173
| streams | Complete | 4/4 |
169174
| fp | Complete | 6/6 |
@@ -173,4 +178,4 @@ src/main/java/org/nkcoder/
173178
| generics | Not Started | 0/5 |
174179
| exceptions | Not Started | 0/4 |
175180
| io | Not Started | 0/3 |
176-
| **Total** | | **25/55**|
181+
| **Total** | | **31/63**|
Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
package org.nkcoder.concurrency.atomic;
2+
3+
import java.util.concurrent.ExecutorService;
4+
import java.util.concurrent.Executors;
5+
import java.util.concurrent.TimeUnit;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
8+
/**
9+
* AtomicInteger: Lock-free thread-safe integer operations.
10+
*
11+
* <p>Key concepts:
12+
* <ul>
13+
* <li>Uses CAS (Compare-And-Swap) hardware instructions</li>
14+
* <li>No locks, no blocking - better performance under contention</li>
15+
* <li>Individual operations are atomic, not compound operations</li>
16+
* <li>Great for counters, sequence generators, statistics</li>
17+
* </ul>
18+
*
19+
* <p>Interview tip: Understand CAS and why incrementAndGet is atomic but
20+
* get() followed by set() is not.
21+
*/
22+
public class AtomicIntegerExample {
23+
24+
public static void main(String[] args) throws Exception {
25+
whyAtomicNeeded();
26+
basicOperations();
27+
atomicIncrementDecrement();
28+
compareAndSet();
29+
accumulateAndUpdate();
30+
atomicVsSynchronized();
31+
bestPractices();
32+
}
33+
34+
static void whyAtomicNeeded() throws Exception {
35+
System.out.println("=== Why Atomic Operations Are Needed ===");
36+
37+
// Non-atomic increment (BROKEN)
38+
class BrokenCounter {
39+
private int count = 0;
40+
41+
void increment() {
42+
count++; // NOT atomic! Read-modify-write
43+
}
44+
45+
int get() { return count; }
46+
}
47+
48+
// Atomic increment (CORRECT)
49+
class SafeCounter {
50+
private final AtomicInteger count = new AtomicInteger(0);
51+
52+
void increment() {
53+
count.incrementAndGet(); // Atomic operation
54+
}
55+
56+
int get() { return count.get(); }
57+
}
58+
59+
int iterations = 10_000;
60+
int threads = 10;
61+
62+
// Test broken counter
63+
BrokenCounter broken = new BrokenCounter();
64+
try (ExecutorService executor = Executors.newFixedThreadPool(threads)) {
65+
for (int i = 0; i < iterations; i++) {
66+
executor.submit(broken::increment);
67+
}
68+
}
69+
System.out.println(" Broken counter: " + broken.get() + " (expected " + iterations + ")");
70+
71+
// Test safe counter
72+
SafeCounter safe = new SafeCounter();
73+
try (ExecutorService executor = Executors.newFixedThreadPool(threads)) {
74+
for (int i = 0; i < iterations; i++) {
75+
executor.submit(safe::increment);
76+
}
77+
}
78+
System.out.println(" Atomic counter: " + safe.get() + " (expected " + iterations + ")");
79+
80+
System.out.println("""
81+
82+
Why count++ is not atomic:
83+
1. Read current value from memory
84+
2. Add 1 to the value
85+
3. Write new value back to memory
86+
87+
Two threads can read the same value, both add 1,
88+
and both write back - losing one increment!
89+
""");
90+
}
91+
92+
static void basicOperations() {
93+
System.out.println("=== Basic Operations ===");
94+
95+
AtomicInteger atomic = new AtomicInteger(10);
96+
97+
// get() - read current value
98+
System.out.println(" get(): " + atomic.get());
99+
100+
// set() - write new value
101+
atomic.set(20);
102+
System.out.println(" set(20): " + atomic.get());
103+
104+
// getAndSet() - atomically set and return old value
105+
int old = atomic.getAndSet(30);
106+
System.out.println(" getAndSet(30): old=" + old + ", new=" + atomic.get());
107+
108+
// lazySet() - eventual write (no memory barrier, faster)
109+
atomic.lazySet(40);
110+
System.out.println(" lazySet(40): " + atomic.get());
111+
112+
System.out.println();
113+
}
114+
115+
static void atomicIncrementDecrement() {
116+
System.out.println("=== Atomic Increment/Decrement ===");
117+
118+
AtomicInteger counter = new AtomicInteger(0);
119+
120+
// incrementAndGet() - add 1, return NEW value
121+
System.out.println(" incrementAndGet(): " + counter.incrementAndGet()); // 1
122+
123+
// getAndIncrement() - return OLD value, then add 1
124+
System.out.println(" getAndIncrement(): " + counter.getAndIncrement()); // 1 (now 2)
125+
126+
// decrementAndGet() - subtract 1, return NEW value
127+
System.out.println(" decrementAndGet(): " + counter.decrementAndGet()); // 1
128+
129+
// getAndDecrement() - return OLD value, then subtract 1
130+
System.out.println(" getAndDecrement(): " + counter.getAndDecrement()); // 1 (now 0)
131+
132+
// addAndGet() - add N, return NEW value
133+
System.out.println(" addAndGet(5): " + counter.addAndGet(5)); // 5
134+
135+
// getAndAdd() - return OLD value, then add N
136+
System.out.println(" getAndAdd(3): " + counter.getAndAdd(3)); // 5 (now 8)
137+
138+
System.out.println(" Final value: " + counter.get()); // 8
139+
140+
System.out.println("""
141+
142+
Pattern:
143+
- getAndXxx(): Returns OLD value, then applies operation
144+
- xxxAndGet(): Applies operation, then returns NEW value
145+
146+
All operations are atomic (single CAS instruction).
147+
""");
148+
}
149+
150+
static void compareAndSet() {
151+
System.out.println("=== Compare-And-Set (CAS) ===");
152+
153+
AtomicInteger atomic = new AtomicInteger(10);
154+
155+
// compareAndSet(expected, update)
156+
// Only updates if current value equals expected
157+
boolean success1 = atomic.compareAndSet(10, 20); // expect 10, set to 20
158+
System.out.println(" CAS(10, 20): " + success1 + ", value=" + atomic.get());
159+
160+
boolean success2 = atomic.compareAndSet(10, 30); // expect 10, but it's 20 now
161+
System.out.println(" CAS(10, 30): " + success2 + ", value=" + atomic.get());
162+
163+
// CAS loop pattern - retry until success
164+
System.out.println("\n CAS loop pattern (multiply by 2):");
165+
int oldValue, newValue;
166+
do {
167+
oldValue = atomic.get();
168+
newValue = oldValue * 2;
169+
} while (!atomic.compareAndSet(oldValue, newValue));
170+
System.out.println(" Result: " + atomic.get());
171+
172+
System.out.println("""
173+
174+
CAS is the foundation of lock-free algorithms:
175+
1. Read current value
176+
2. Compute new value
177+
3. CAS: if still same, update; else retry
178+
179+
This is what incrementAndGet() does internally!
180+
""");
181+
}
182+
183+
static void accumulateAndUpdate() {
184+
System.out.println("=== Accumulate and Update ===");
185+
186+
AtomicInteger atomic = new AtomicInteger(10);
187+
188+
// updateAndGet(function) - apply function atomically
189+
int result1 = atomic.updateAndGet(x -> x * 2);
190+
System.out.println(" updateAndGet(x -> x * 2): " + result1); // 20
191+
192+
// getAndUpdate(function) - return old, then apply
193+
int result2 = atomic.getAndUpdate(x -> x + 5);
194+
System.out.println(" getAndUpdate(x -> x + 5): old=" + result2 + ", new=" + atomic.get());
195+
196+
// accumulateAndGet(value, function) - combine with another value
197+
int result3 = atomic.accumulateAndGet(3, (current, x) -> current * x);
198+
System.out.println(" accumulateAndGet(3, (c,x) -> c*x): " + result3); // 25 * 3 = 75
199+
200+
// getAndAccumulate(value, function)
201+
int result4 = atomic.getAndAccumulate(2, Integer::sum);
202+
System.out.println(" getAndAccumulate(2, Integer::sum): old=" + result4); // 75 (now 77)
203+
204+
System.out.println(" Final value: " + atomic.get());
205+
206+
System.out.println("""
207+
208+
These methods handle the CAS loop internally:
209+
- updateAndGet(): f(current) -> new
210+
- accumulateAndGet(): f(current, given) -> new
211+
212+
Cleaner than writing your own CAS loop!
213+
""");
214+
}
215+
216+
static void atomicVsSynchronized() throws Exception {
217+
System.out.println("=== Atomic vs Synchronized ===");
218+
219+
int operations = 1_000_000;
220+
221+
// Synchronized counter
222+
class SyncCounter {
223+
private int count = 0;
224+
synchronized void increment() { count++; }
225+
synchronized int get() { return count; }
226+
}
227+
228+
// Atomic counter
229+
AtomicInteger atomicCounter = new AtomicInteger(0);
230+
231+
// Benchmark synchronized
232+
SyncCounter sync = new SyncCounter();
233+
long start1 = System.nanoTime();
234+
try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
235+
for (int i = 0; i < operations; i++) {
236+
executor.submit(sync::increment);
237+
}
238+
}
239+
long syncTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start1);
240+
241+
// Benchmark atomic
242+
long start2 = System.nanoTime();
243+
try (ExecutorService executor = Executors.newFixedThreadPool(4)) {
244+
for (int i = 0; i < operations; i++) {
245+
executor.submit(atomicCounter::incrementAndGet);
246+
}
247+
}
248+
long atomicTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start2);
249+
250+
System.out.println(" Synchronized: " + syncTime + "ms, count=" + sync.get());
251+
System.out.println(" Atomic: " + atomicTime + "ms, count=" + atomicCounter.get());
252+
253+
System.out.println("""
254+
255+
+-------------------+-----------------------+-----------------------+
256+
| Feature | synchronized | AtomicInteger |
257+
+-------------------+-----------------------+-----------------------+
258+
| Mechanism | Lock (monitor) | CAS (hardware) |
259+
| Blocking | Yes | No (spins/retries) |
260+
| Compound ops | Yes (any code block) | Limited (single op) |
261+
| Contention | Slower (waiting) | Faster (retrying) |
262+
| Memory visibility | Full barrier | Volatile semantics |
263+
+-------------------+-----------------------+-----------------------+
264+
265+
Use Atomic when:
266+
- Single variable updates
267+
- Simple increment/compare operations
268+
- High contention expected
269+
270+
Use synchronized when:
271+
- Multiple variables must update together
272+
- Complex logic in critical section
273+
- Need to wait on conditions
274+
""");
275+
}
276+
277+
static void bestPractices() {
278+
System.out.println("=== Best Practices ===");
279+
280+
System.out.println("""
281+
DO:
282+
- Use AtomicInteger for counters, sequence numbers
283+
- Use updateAndGet/accumulateAndGet for custom operations
284+
- Prefer atomic classes for single-variable thread safety
285+
286+
DON'T:
287+
- Use for compound operations (check-then-act)
288+
- Assume multiple atomic ops are atomic together:
289+
290+
// BROKEN - not atomic as a whole!
291+
if (counter.get() == 0) {
292+
counter.set(1);
293+
}
294+
295+
// CORRECT - single atomic operation
296+
counter.compareAndSet(0, 1);
297+
298+
Related classes:
299+
- AtomicLong, AtomicBoolean - other primitives
300+
- AtomicReference<T> - for objects
301+
- AtomicIntegerArray - atomic array operations
302+
- LongAdder/LongAccumulator - better for high contention
303+
304+
Performance tip:
305+
For very high contention counters, LongAdder is faster
306+
than AtomicLong because it reduces contention by spreading
307+
updates across multiple cells.
308+
""");
309+
}
310+
}

0 commit comments

Comments
 (0)