Reflection, Generics Type Erasure, ConcurrentHashMap Internals, and Thread Contention
How ConcurrentHashMap Achieves Thread Safety
Section titled “How ConcurrentHashMap Achieves Thread Safety”Unlike Hashtable, which synchronizes on the entire map for every operation, ConcurrentHashMap (Java 8+) avoids locking the whole structure:
- Writes use CAS (compare-and-swap) operations where possible, and fall back to a
synchronizedblock scoped to just the head node of the affected bucket — not the whole map. - Reads are largely lock-free, relying on
volatilereads of the underlyingNodearray so readers always see a consistent (if possibly slightly stale) view without blocking on writers. - Buckets that accumulate too many collisions are converted from a linked list to a red-black tree (same optimization as
HashMapsince Java 8), keeping worst-case lookup at O(log n) instead of O(n).
Hashtable: one lock guards the entire mapConcurrentHashMap: lock scope = one bucket, most reads lock-freeThis is why ConcurrentHashMap scales far better than Hashtable or Collections.synchronizedMap() under concurrent access — contention is spread across buckets instead of serializing every operation through a single lock.
Reflection
Section titled “Reflection”The Reflection API (java.lang.reflect) lets code inspect and manipulate classes, methods, fields, and constructors at runtime — including private members, bypassing normal access checks via setAccessible(true).
Class<?> clazz = Employee.class;
Method[] methods = clazz.getDeclaredMethods();
Field field = clazz.getDeclaredField("salary");field.setAccessible(true);field.set(employeeInstance, 75000);When It’s Used
Section titled “When It’s Used”- Frameworks (Spring, Hibernate) instantiating beans/entities and reading annotations without the application code knowing the concrete type ahead of time.
- Serialization libraries (Jackson, Gson) mapping JSON fields to object fields generically.
- Dependency injection containers wiring
@Autowiredfields. - Testing tools accessing private state for assertions.
Trade-offs
Section titled “Trade-offs”- Noticeably slower than direct method/field access (JIT can’t inline as effectively).
- Breaks encapsulation and bypasses compile-time type checking — errors that would normally be caught by the compiler surface as runtime exceptions instead.
Stream API Internal Working and Lazy Evaluation
Section titled “Stream API Internal Working and Lazy Evaluation”Intermediate operations (filter, map, sorted, …) don’t process any data when called — they just record a stage in a pipeline built on top of a Spliterator. Nothing actually runs until a terminal operation (collect, forEach, reduce, …) is invoked.
When the terminal operation runs, each element flows through the entire pipeline one at a time (this is sometimes called “vertical” execution), rather than running each stage across the whole collection before moving to the next stage. This is what makes short-circuiting operations like findFirst() or limit() efficient — the pipeline can stop pulling elements from the source as soon as enough have satisfied the terminal operation, instead of eagerly processing everything upfront.
List<String> result = names.stream() .filter(n -> { System.out.println("filtering " + n); return n.startsWith("A"); }) .map(n -> { System.out.println("mapping " + n); return n.toUpperCase(); }) .findFirst() .orElse(null);For a name that doesn’t start with “A”, you’ll see filtering printed but never mapping — and once a match is found, no further elements are pulled from the source at all.
Type Erasure in Generics
Section titled “Type Erasure in Generics”Java generics exist only at compile time. The compiler uses generic type information for type checking, then erases it when generating bytecode — replacing type parameters with Object (or their bound, if one is specified) and inserting casts where needed.
List<String> strings = new ArrayList<>();List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass()); // trueBoth have the same runtime Class object — List — because the <String>/<Integer> information doesn’t exist at runtime.
Consequences of erasure:
- You can’t create a generic array directly (
new T[10]doesn’t compile). - You can’t use
instanceofwith a parameterized type (obj instanceof List<String>doesn’t compile — onlyobj instanceof List). - The compiler generates bridge methods to preserve polymorphism when a generic method is overridden with a more specific type.
CompletableFuture vs Traditional Multithreading
Section titled “CompletableFuture vs Traditional Multithreading”Traditional (Thread/ExecutorService + Future) |
CompletableFuture |
|---|---|
Future.get() blocks the calling thread |
Non-blocking composition via thenApply/thenCompose |
| No built-in way to chain follow-up work | Fluent chaining (thenApply, thenAccept, thenCompose) |
| Combining multiple results requires manual coordination | thenCombine/allOf/anyOf compose multiple futures directly |
Exception handling requires try/catch around get() |
exceptionally/handle integrate into the chain |
You manage your own ExecutorService |
Uses the common ForkJoinPool by default, or a supplied executor |
Diagnosing Thread Contention in Production
Section titled “Diagnosing Thread Contention in Production”Where memory leak investigation focuses on heap dumps, thread contention investigation focuses on thread dumps:
jstack <pid>or, without needing the PID resolved separately:
jcmd <pid> Thread.printLook for:
- Many threads in
BLOCKEDstate, all waiting on the same lock/monitor — a strong signal of lock contention on a hot resource. - The thread currently holding the contended lock (shown in the dump) — that’s the one to investigate first, not the blocked ones.
- Repeated dumps a few seconds apart, to distinguish a genuinely stuck thread from one that’s just slow.
Tools: VisualVM’s thread view, async-profiler’s lock-profiling mode, and Java Flight Recorder for capturing contention events with lower overhead than repeated thread dumps.
Key Takeaways
Section titled “Key Takeaways”ConcurrentHashMapscales under load because it locks at bucket granularity (and reads lock-free), unlikeHashtable’s single map-wide lock.- Reflection trades performance and compile-time safety for the flexibility frameworks need to work generically over unknown types.
- Streams are lazy: nothing executes until a terminal operation runs, and elements flow through the whole pipeline one at a time — this is what makes
findFirst()/limit()avoid unnecessary work. - Generics are erased at compile time —
List<String>andList<Integer>are the same class at runtime, which is why generic arrays andinstanceofwith type parameters aren’t allowed. - For thread contention (as opposed to memory leaks), reach for thread dumps (
jstack/jcmd Thread.print) rather than heap dumps — look for clusters ofBLOCKEDthreads and identify who’s holding the contended lock.