Performance & Optimization
Concurrent Performance
- Amdahl: 5% serial ⇒ max 20× speedup, on any core count
- Reduce lock scope (get in, get out), then lock granularity (stripe), then remove locks (CAS, confinement)
- Contention costs more than blocking: cache-line ping-pong, context switches
LongAdderoverAtomicLongfor hot counters;ConcurrentHashMapover any synchronized map- False sharing pads out (Hardware Memory); per-thread state beats shared state
- Measure contention with JFR lock events, not intuition
JCiP ch. 11 frames it: concurrency's costs are context switches (a blocked thread parks — microseconds plus cache-refill), memory synchronization (fences defeat compiler/CPU optimizations), and contention (the serializer). The scalability ladder, in order of preference: don't share (thread confinement, per-request objects), share immutably (Immutability Class Design), share with CAS (Atomics Nonblocking), share with striped/short locks, and only then a single coarse lock.
// BAD: I/O and parsing inside the lock — the whole request serializes here
synchronized (cache) {
if (!cache.containsKey(key)) cache.put(key, parse(fetch(key)));
return cache.get(key);
}
// GOOD: the map handles atomicity; expensive work runs unlocked per key
return cache.computeIfAbsent(key, k -> parse(fetch(k))); // ConcurrentHashMap
// Hot counter: striped cells absorb write contention
private final LongAdder hits = new LongAdder();Lock granularity: one lock per hash bin (what ConcurrentHashMap does) instead of one per map multiplies throughput under write contention; per-connection instead of per-server; per-account instead of per-bank (with an ordering discipline). The end state of granularity shrinking is no lock: a single-writer design (one thread owns the state, others send messages via a queue) often out-scales clever locking entirely.