| ConcurrentHashMap | Thread-safe HashMap; bucket-level locking; no global lock |
| CopyOnWriteArrayList | Snapshot on write; zero-cost reads; expensive writes |
| ArrayBlockingQueue | Bounded blocking queue; one lock for put+take |
| LinkedBlockingQueue | Optionally bounded; separate put/take locks |
| ConcurrentLinkedQueue | Lock-free FIFO; non-blocking; unbounded |
| ConcurrentSkipListMap | Thread-safe SortedMap; O(log n); no locking |
| SynchronousQueue | Zero capacity; hand-off between threads |
ConcurrentHashMap Interview Questions
Q1. How does ConcurrentHashMap achieve thread safety without a global lock?
A: Java 8 ConcurrentHashMap: uses synchronized on individual buckets (CAS for first entry, synchronized for subsequent entries in the same bucket). Multiple threads can write to different buckets concurrently. Read operations are mostly lock-free (volatile fields). Java 7 used 16 segments (ReentrantLock per segment). Java 8 implementation is more granular — scales to the number of CPU cores.
Q2. What is the difference between ConcurrentHashMap and Hashtable?
A: Hashtable: synchronized on every method with a single lock (entire map locked per operation). ConcurrentHashMap: fine-grained locking (bucket-level), better concurrency, no lock for reads. Hashtable: legacy class, doesn't allow null keys/values. ConcurrentHashMap: also no null keys/values (null could mean "missing" in concurrent scenarios — ambiguous). Use ConcurrentHashMap in all new concurrent code.
Q3. Why doesn't ConcurrentHashMap allow null keys or values?
A: In a concurrent context, map.get(key) returning null is ambiguous: does the key map to null, or is the key absent? With HashMap, you can follow up with containsKey(key). In ConcurrentHashMap, between get() and containsKey(), another thread may have removed the key — the state may change. Disallowing null avoids this ambiguity entirely.
Q4. What is computeIfAbsent in ConcurrentHashMap?
A: computeIfAbsent(key, mappingFunction): atomically computes and inserts a value if key is absent. The mapping function is called at most once per key (guaranteed atomic in Java 8+). Eliminates the check-then-act race condition of if(!map.containsKey(key)) map.put(key, compute()). Use for thread-safe lazy initialization in a shared map.
ConcurrentHashMap<String, List<String>> multimap = new ConcurrentHashMap<>();
// Thread-safe way to build a multimap:
multimap.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>()).add(value);
Q5. What is the merge() method in ConcurrentHashMap?
A: merge(key, value, remappingFunction): if key absent, put value; if present, compute new value using remappingFunction(existing, value). If remappingFunction returns null, remove the key. Atomic. Excellent for counter/accumulator patterns.
ConcurrentHashMap<String, Integer> wordCount = new ConcurrentHashMap<>();
// Thread-safe word count:
words.forEach(word -> wordCount.merge(word, 1, Integer::sum));
Q6. What does ConcurrentHashMap.size() return?
A: size() returns an approximation — not an exact count. In a concurrent map, the size changes continuously; returning a point-in-time exact count would require locking the entire map. mappingCount() (Java 8+) returns a long and is preferred for large maps. For exact counts, use a separate AtomicLong counter if needed.
Q7. What are bulk operations in ConcurrentHashMap (Java 8)?
A: ConcurrentHashMap added bulk operations that work in parallel: forEach(parallelismThreshold, action), search(parallelismThreshold, searchFunction), reduce(parallelismThreshold, transformer, reducer). When map.size() >= parallelismThreshold, operations use ForkJoinPool. Set parallelismThreshold to 1 for maximum parallelism, Long.MAX_VALUE for sequential.
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// Parallel reduce to find max value:
int maxVal = map.reduce(4, // parallel if size >= 4
(k, v) -> v, // transformer
Integer::max); // reducer
Q8. What is the weakly consistent iterator of ConcurrentHashMap?
A: ConcurrentHashMap iterators are weakly consistent: they reflect the state at or after iterator creation. Elements may be added/removed concurrently — iterator MAY or MAY NOT reflect these changes. Never throws ConcurrentModificationException. This is the trade-off for lock-free iteration — perfect for logging or non-critical scans.
Q9. How is ConcurrentHashMap different from Collections.synchronizedMap?
A: Collections.synchronizedMap: wraps any map with single synchronized mutex — every operation (get, put, containsKey) locks the ENTIRE map. ConcurrentHashMap: bucket-level CAS/synchronized — multiple threads write concurrently. Key difference: compound operations on synchronizedMap still need external synchronization; ConcurrentHashMap provides atomic compound ops (computeIfAbsent, merge, etc.).
Q10. What is the ConcurrentHashMap internal structure in Java 8?
A: Internal: array of Node<K,V> buckets (initially 16). Collision: singly-linked list. When bucket has >= 8 entries (TREEIFY_THRESHOLD) AND total size >= 64, bucket converts to red-black tree (TreeNode) — O(log n) lookup. Put: CAS for empty bucket, synchronized on bucket head for non-empty. Resize: transfers to doubled array while serving concurrent reads.
CopyOnWriteArrayList Interview Questions
Q11. How does CopyOnWriteArrayList achieve thread safety?
A: On every write (add, set, remove), it creates a COPY of the underlying array, modifies the copy, then atomically replaces the reference to the new array. Reads use the current snapshot — no lock needed. The array reference is volatile, ensuring visibility. Iteration uses the snapshot at iterator creation time — zero ConcurrentModificationException risk.
Q12. When should you use CopyOnWriteArrayList?
A: Ideal when reads vastly outnumber writes and list is small. Use cases: listener/observer lists (many iterations, rare add/remove), read-heavy configuration lists. Avoid when: frequent writes (each write copies entire array — O(n) time and space), large lists (copying is expensive). Alternative for write-heavy: ConcurrentLinkedQueue or synchronized collections.
Q13. What happens if you modify a CopyOnWriteArrayList during iteration?
A: Modification (add/remove) creates a new array copy and updates the reference. The ongoing iterator holds a reference to the OLD array — it continues iterating the old snapshot. No exception, no effect on iterator. The iterator sees the state at the time of its creation. This is guaranteed by the copy-on-write semantics.
Q14. Does CopyOnWriteArrayList support ListIterator.remove()?
A: No — CopyOnWriteArrayList's iterator (snapshot) does not support remove(), set(), or add(). These throw UnsupportedOperationException. Use the list's own remove() method externally. This is intentional — the iterator operates on an immutable snapshot.
Blocking Queue Interview Questions
Q15. What is a BlockingQueue and its key methods?
A: BlockingQueue extends Queue with blocking operations. Four operation types for each insert/remove: Throw exception (add/remove/element), Return special value (offer/poll/peek), Block indefinitely (put/take), Block with timeout (offer(e,t,u)/poll(t,u)). Use put/take for producer-consumer; offer/poll with timeout for timed operations.
BlockingQueue<String> q = new ArrayBlockingQueue<>(100);
q.put("item"); // blocks if full
String s = q.take(); // blocks if empty
q.offer("item", 1, TimeUnit.SECONDS); // wait up to 1s
String s2 = q.poll(1, TimeUnit.SECONDS); // wait up to 1s for item
Q16. What is the difference between ArrayBlockingQueue and LinkedBlockingQueue?
| Feature | ArrayBlockingQueue | LinkedBlockingQueue |
|---|---|---|
| Capacity | Fixed (bounded) | Optional (defaults to MAX_INT) |
| Locking | Single lock (put+take share) | Separate put/take locks |
| Memory | Pre-allocated array | Node objects per element |
| Throughput | Lower (shared lock) | Higher (separate put/take) |
Q17. What is PriorityBlockingQueue?
A: PriorityBlockingQueue: unbounded blocking queue with priority ordering. Elements must implement Comparable or use a Comparator. take() returns highest-priority element. Put never blocks (unbounded). Use for task scheduling where tasks have priority levels. Not FIFO for equal-priority elements.
Q18. What is SynchronousQueue?
A: SynchronousQueue: zero-capacity "hand-off" queue. put() blocks until another thread calls take() (direct transfer). No buffering. Used in newCachedThreadPool() — submitter hands task directly to an idle thread. Also useful for thread-to-thread rendezvous without extra buffering. Fair mode: FIFO; unfair: LIFO.
Q19. What is DelayQueue?
A: DelayQueue<E extends Delayed>: unbounded blocking queue where elements become available only after their delay expires. Elements must implement Delayed (getDelay() and compareTo()). take() blocks until the next element's delay expires. Use: caching with time-to-live, scheduling future tasks, session expiry.
class DelayedTask implements Delayed {
private final long startTime;
DelayedTask(long delayMs) { this.startTime = System.currentTimeMillis() + delayMs; }
public long getDelay(TimeUnit unit) {
return unit.convert(startTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
public int compareTo(Delayed o) { return Long.compare(getDelay(TimeUnit.MS), o.getDelay(TimeUnit.MS)); }
}
Q20. What is LinkedTransferQueue?
A: LinkedTransferQueue (Java 7): unbounded blocking queue combining SynchronousQueue + LinkedBlockingQueue features. transfer(e): like put() but blocks until a consumer takes the element (synchronous hand-off). tryTransfer(e): immediate hand-off if consumer waiting, else fails. Useful for reducing latency by directly handing off to waiting consumers before queuing.
Q21. What is the Deque and BlockingDeque?
A: Deque (double-ended queue): add/remove from both ends. BlockingDeque: blocking operations on both ends. LinkedBlockingDeque is the standard implementation. ConcurrentLinkedDeque is lock-free. Used for: work-stealing queues (ForkJoinPool), undo/redo stacks with concurrent access, producer-consumer with priority for "latest" items at head.
Q22. What is ConcurrentSkipListMap?
A: ConcurrentSkipListMap: thread-safe SortedMap/NavigableMap backed by skip list (probabilistic data structure with O(log n) operations). Lock-free implementation. Supports: subMap(), headMap(), tailMap(), higherKey(), floorKey(), ceilingKey(). Use when you need a sorted concurrent map. TreeMap wrapped in synchronizedMap is an alternative but has global lock.
Q23. What is ConcurrentSkipListSet?
A: Thread-safe SortedSet backed by ConcurrentSkipListMap. Like TreeSet but concurrent. Supports pollFirst(), pollLast(), headSet(), tailSet(). No equivalent in synchronized wrappers (synchronizedSortedSet exists but has global lock). Use when you need a sorted, concurrent set with range operations.
Q24. What is the difference between ConcurrentLinkedQueue and LinkedBlockingQueue?
A: ConcurrentLinkedQueue: non-blocking (poll() returns null if empty), lock-free (CAS-based), unbounded. LinkedBlockingQueue: blocking (take() blocks until element available), locks-based, optionally bounded. Use ConcurrentLinkedQueue for non-blocking algorithms; LinkedBlockingQueue for producer-consumer with back-pressure.
Q25. How do you implement a thread-safe LRU cache?
// Using LinkedHashMap with synchronized:
class LRUCache<K,V> {
private final Map<K,V> map;
LRUCache(int capacity) {
map = Collections.synchronizedMap(
new LinkedHashMap<K,V>(capacity, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry e) {
return size() > capacity;
}
}
);
}
public V get(K key) { return map.get(key); }
public void put(K key, V value) { map.put(key, value); }
}
// Or use Caffeine cache library for production-grade LRU with async loading
Java Memory Model (JMM) Interview Questions
Q26. What is the Java Memory Model (JMM)?
A: JMM (JSR-133, Java 5) specifies how threads interact through memory. It defines: what values a thread can see when reading a shared variable, the ordering of operations across threads, and when writes become visible. JMM uses happens-before as the formal ordering guarantee. Without JMM rules, compilers and CPUs can reorder operations in ways that break concurrent programs.
Q27. What is the happens-before relationship?
A: Happens-before (HB): if action A happens-before action B, all effects of A are visible to B. HB rules in JMM: 1) Program order within a thread. 2) Monitor unlock happens-before subsequent lock of same monitor. 3) Volatile write happens-before subsequent volatile read of same variable. 4) Thread.start() happens-before any action in the started thread. 5) All actions in a thread happen-before Thread.join() returns. 6) Object construction happens-before the finalizer.
Q28. What is instruction reordering and why does it happen?
A: CPUs and compilers reorder instructions for performance. Single-threaded: reordering is transparent (program-order semantics maintained). Multi-threaded: reordering can cause one thread to see another thread's writes in unexpected order. Example: x = 1; ready = true; could be reordered to ready = true; x = 1; — another thread seeing ready=true might miss x=1. Volatile/synchronized prevents problematic reorderings.
Q29. How does volatile prevent reordering?
A: JMM: a volatile write is a happens-before relative to a subsequent volatile read of the same variable. This means: all writes before the volatile write are visible to the thread reading the volatile variable. Effectively: a volatile write flushes all pending writes; a volatile read loads fresh values. This prevents the reordering example where ready=true appears before x=1 from another thread's perspective.
volatile boolean ready = false;
int x = 0;
// Thread 1:
x = 42;
ready = true; // volatile write: x=42 visible before ready=true
// Thread 2:
if (ready) { // volatile read
System.out.println(x); // guaranteed to see 42
}
Q30. What is the difference between final fields and volatile for safe publication?
A: final fields: once constructor completes, all threads see the correct final field values — NO synchronization needed at read sites. Only works for fields initialized in constructor. volatile: ensures write immediately visible to readers — works for any mutable field. For immutable objects, final is more efficient (no read barriers). For mutable shared state, use volatile or synchronized.
Q31. What is the word-tearing problem?
A: Word-tearing: when multiple threads write to different parts of the same memory word simultaneously. JVM guarantees: byte, short, int, char, float, Object reference reads/writes are atomic. long and double (64-bit) are NOT atomic on 32-bit JVMs — two separate 32-bit writes. Use volatile long/double to guarantee atomic reads/writes for 64-bit values.
Q32. What is the total store ordering (TSO) model on x86?
A: x86 CPUs use TSO: store-store and load-load reorderings don't happen, but store-load can (write to memory before reading is visible). JMM is weaker than TSO — JMM allows more reorderings. Java code with proper volatile/synchronized is correct on all architectures (x86, ARM, Power). Pure x86 Java code may accidentally work without volatile due to TSO, but breaks on ARM (weaker memory model).
Q33. What is the piggybacking technique for synchronization?
A: Piggybacking: leveraging an existing synchronization operation to establish happens-before for other data — without adding new locks. Example: update a non-volatile variable, then write a volatile sentinel variable. Readers read the volatile sentinel, then read the non-volatile variable — HB chain ensures visibility. Fragile and tricky; document clearly. Usually better to just use volatile or synchronized directly.
Q34. What is the MESI cache coherence protocol?
A: MESI: Modified, Exclusive, Shared, Invalid — states for CPU cache lines in multi-core systems. When Thread A writes (volatile), its cache line transitions to Modified — other CPU caches' copies become Invalid. Thread B reading sees Invalid, fetches fresh value from main memory or A's cache. JMM's visibility guarantees are implemented by the CPU's cache coherence protocol at hardware level.
Q35. What is the happens-before for Thread.interrupt()?
A: Thread.interrupt() happens-before the interrupted thread detects the interruption (via isInterrupted(), interrupted(), or catching InterruptedException). All writes by the interrupting thread before the interrupt() call are visible to the interrupted thread when it detects the interruption. This is part of JMM's guarantee for thread coordination.
Q36. Does ConcurrentHashMap guarantee visibility across operations?
A: Yes — ConcurrentHashMap operations that succeed (put, get, etc.) establish happens-before between the put and subsequent get: a successful put happens-before any subsequent get that reads the same key. Iterators see a "snapshot" but still respect happens-before for elements they return. This is stronger than regular HashMap but weaker than always seeing the absolute latest value.
Q37. How does JMM handle object creation visibility?
A: JMM: completion of an object's constructor happens-before the object's finalizer. But this doesn't mean safe publication — the reference to the object must still be safely published. Constructor completion doesn't automatically make the object visible to other threads. The reference must be written to a volatile, synchronized, or final field for other threads to safely see the fully-constructed object.
Q38. What is reachability-based GC and weak references?
A: JVM GC collects objects with no strong references. Reference types: Strong (normal), Soft (collected when memory low — for caches), Weak (collected at next GC — for canonicalization), Phantom (for cleanup after GC). WeakHashMap keys are weak references — entries automatically removed when keys are collected. Use for canonical maps, intern pools. SoftReference for memory-sensitive caches.
// SoftReference for cache:
SoftReference<BigData> cache = new SoftReference<>(loadData());
BigData data = cache.get(); // null if collected
if (data == null) {
data = loadData();
cache = new SoftReference<>(data);
}
Q39. What is a reference queue?
A: ReferenceQueue: a queue that the GC enqueues soft/weak/phantom references into when their referents are collected. Allows cleanup code to run when objects are GCed. WeakHashMap uses a ReferenceQueue internally to remove stale entries. Phantom references with ReferenceQueue enable post-finalization cleanup (safer than finalize()).
Q40. What is the relationship between BlockingQueue and the producer-consumer pattern?
// Clean producer-consumer with BlockingQueue:
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(1000);
// Producers:
ExecutorService producers = Executors.newFixedThreadPool(4);
producers.submit(() -> {
while (!done) { queue.put(generateTask()); }
});
// Consumers:
ExecutorService consumers = Executors.newFixedThreadPool(8);
consumers.submit(() -> {
while (!done || !queue.isEmpty()) {
Task t = queue.poll(100, TimeUnit.MILLISECONDS);
if (t != null) process(t);
}
});
Q41. What is the Java 21 SequencedCollection for concurrent use?
A: Java 21 SequencedCollection adds getFirst()/getLast() to collections. For concurrent use: ConcurrentLinkedDeque implements SequencedCollection — thread-safe deque with getFirst()/getLast(). LinkedBlockingDeque also qualifies. These provide uniform API for "head" and "tail" access in concurrent scenarios without casting to specific types.
Q42. How does ConcurrentHashMap handle resize?
A: Java 8 CHM uses "transfer" for resize: creates a new array double the size, marks old buckets as forwarding nodes during transfer. Multiple threads can help transfer (cooperative resize). Reads during resize see either old or new bucket data — always correct due to forwarding nodes. No global lock during resize — concurrent reads and writes continue.
Q43. What is a concurrent deque and when to use it?
A: ConcurrentLinkedDeque: thread-safe, non-blocking, unbounded deque. Add/remove from both ends (head/tail). Lock-free CAS-based. Use for: LRU cache (remove from tail, add to head), work-stealing (local thread adds/removes from head; stealer removes from tail), priority queuing with two priority levels.
Q44. What is the compareAndSet memory ordering guarantee?
A: A successful CAS establishes happens-before between the CAS write and subsequent reads of the same variable. A failed CAS has no memory ordering guarantee (just an ordinary read). This means lock-free algorithms built with CAS get visibility guarantees for successful updates — threads that see the new value are guaranteed to also see all writes before the CAS.
Q45. What is the initialization safety guarantee for final fields?
A: JMM: a thread that reads a reference to a freshly-constructed object sees the final field values as set by the constructor — even without any synchronization. However: 1) All final fields must be set in the constructor body. 2) "this" must not escape during construction. 3) The reference to the object must be published safely (but readers don't need additional synchronization to read final fields).
Q46. What is the volatile write-then-read pattern?
A: Writing a volatile variable ensures all prior writes are flushed to memory. Reading a volatile variable ensures all subsequent reads see fresh values. Pattern: write all data, then write volatile flag. Read volatile flag, then read all data. The volatile creates an ordering barrier. This is weaker than synchronized but cheaper — for simple one-writer-one-reader scenarios.
Q47. How does Java handle out-of-order execution on ARM?
A: ARM has a weaker memory model than x86 — allows more reorderings (load-load, store-store, load-store). Java volatile compiles to appropriate memory barrier instructions per architecture: x86 = mfence (or nothing for weaker barriers), ARM = dmb ish (data memory barrier). JVM abstracts this — Java concurrent code with volatile/synchronized is correct on all architectures automatically.
Q48. What is the publication problem with non-final, non-volatile fields?
// Unsafe: other threads may see n=0 even after init() runs
class Config {
private int n; // non-final, non-volatile
public void init() { n = 100; }
public int getN() { return n; } // may return 0 on some CPUs!
}
// Safe: volatile guarantees visibility
class Config {
private volatile int n; // volatile
public void init() { n = 100; }
public int getN() { return n; } // always sees 100 after init
}
Q49. What is ConcurrentHashMap.forEachKey/Value/Entry?
A: Java 8 ConcurrentHashMap provides parallel bulk operations: forEachKey(parallelism, action), forEachValue(parallelism, action), forEachEntry(parallelism, action), and mapped versions with a transformer function. These use ForkJoinPool for parallelism above the threshold. Weakly-consistent — snapshot semantics during bulk operation.
Q50. What are the key patterns for concurrent data access?
A: 1) Immutable data: no synchronization needed. 2) ThreadLocal: thread-private copies. 3) ConcurrentHashMap: fine-grained concurrent map. 4) CopyOnWrite: read-heavy small collections. 5) BlockingQueue: producer-consumer decoupling. 6) AtomicXxx: single-variable lock-free counters. 7) volatile: single flag visibility. 8) ReadWriteLock: read-heavy complex state. Always choose the simplest mechanism that's correct — avoid premature optimization.
Q51. What is the ConcurrentHashMap.getOrDefault() method?
A: getOrDefault(key, defaultValue) returns the value for key, or defaultValue if key absent. Atomic read — no race between containsKey and get. Available since Java 8 on all Maps, not CHM-specific. CHM-specific atomic methods: putIfAbsent, remove(key,value), replace(key,old,new), computeIfAbsent, computeIfPresent, compute, merge.
Q52. What is a BlockingQueue transfer protocol?
A: When a producer calls put() on a full BlockingQueue, it blocks. When space becomes available (consumer takes an item), the producer is unblocked. This creates natural back-pressure: producers slow down to the speed of consumers. Essential for preventing unbounded memory growth in pipeline-style architectures. With virtual threads (Java 21), blocking is cheap — simpler to design explicit back-pressure.
Q53. What is the difference between putIfAbsent and computeIfAbsent in ConcurrentHashMap?
A: putIfAbsent(key, value): always creates value, then checks if key absent to insert — value wasted if key present. computeIfAbsent(key, mappingFn): only calls mappingFn if key absent — value created lazily. For expensive values (new Connection(), new List()), computeIfAbsent is far better — avoids unnecessary creation. Also, computeIfAbsent is guaranteed to call the function at most once (atomic compute).
Q54. What is CopyOnWriteArraySet?
A: CopyOnWriteArraySet: thread-safe Set backed by CopyOnWriteArrayList. All write operations copy the underlying array. Uniqueness enforced by linear scan (O(n) add/contains — not O(1) like HashSet). Only suitable for very small sets with rare writes and frequent iteration. For larger sets, use ConcurrentSkipListSet.
Q55. What is the overhead of CopyOnWriteArrayList on write?
A: Each add/remove: 1) acquires a write lock. 2) copies entire array (O(n) time and space). 3) modifies copy. 4) replaces volatile reference. The copy is proportional to the list size — O(n) per write. For 10,000-element list: 10,000 object copies per write. This is why CopyOnWrite is only suitable for very small, read-heavy lists (event listeners, configuration items).
Q56. How do concurrent collections provide iteration safety?
A: Different strategies: CopyOnWrite: snapshot at iterator creation — no CME, stale data. ConcurrentHashMap: weakly-consistent — may reflect concurrent updates, no CME. ConcurrentLinkedQueue: weakly-consistent, no CME. BlockingQueues: weakly-consistent. None provide a strongly-consistent view of concurrent changes during iteration — the trade-off for no global locking.
Q57. What is the recommended way to implement a concurrent set from ConcurrentHashMap?
// Java 8+: ConcurrentHashMap.newKeySet()
Set<String> concurrentSet = ConcurrentHashMap.newKeySet();
concurrentSet.add("a");
concurrentSet.contains("a");
// Or:
Set<String> fromMap = Collections.newSetFromMap(new ConcurrentHashMap<>());
Q58. What is memory visibility in Java and why is it a problem?
A: Each CPU core has L1/L2 cache. When Thread A writes x=1 (cached in A's L1), Thread B reading x may still see x=0 (from B's stale cache). Without synchronization, writes are NOT guaranteed to propagate to other CPUs' caches. JMM formalizes when propagation is guaranteed. Platforms without proper synchronization will fail on multi-core systems even if they appear to work on single-core.
Q59. What is the publish-subscribe pattern with concurrent collections?
// Listener list with CopyOnWriteArrayList:
class EventBus {
private final CopyOnWriteArrayList<EventListener> listeners =
new CopyOnWriteArrayList<>();
public void subscribe(EventListener l) { listeners.add(l); }
public void unsubscribe(EventListener l) { listeners.remove(l); }
public void publish(Event e) {
listeners.forEach(l -> l.onEvent(e)); // snapshot — no CME
}
}
Q60. What are common JMM interview gotchas?
A: 1) volatile doesn't make compound operations atomic (i++ is not atomic even on volatile int). 2) synchronized without volatile still has visibility issues if the reading thread never enters synchronized (they must synchronize on the SAME object). 3) final field safety only works for values set in the constructor. 4) Thread.sleep() does NOT create any happens-before relationship. 5) A thread joining another thread sees everything the joined thread did (HB).
Q61. How does Lock striping reduce contention?
A: ConcurrentHashMap (Java 7 style with 16 segments): Thread A accesses key "hello" (hash → segment 3), Thread B accesses key "world" (hash → segment 11) — they use different locks, no contention. Java 8: per-bucket locking (128+ buckets by default) — even less contention. Lock striping principle: partition shared data so independent accesses use independent locks.
Q62. What is the difference between ConcurrentHashMap.size() and mappingCount()?
A: size(): returns int — limited to Integer.MAX_VALUE (2 billion). mappingCount(): returns long — handles maps with more than 2 billion entries. Both are approximate in concurrent maps. CHM uses a counterCell mechanism (similar to LongAdder) internally for the count — threads update local cells, sum is aggregated on read. Prefer mappingCount() for very large maps.
Q63. What happens when a bounded BlockingQueue is full and put() is called?
A: put() blocks (indefinitely) until space is available (consumer removes an item). This provides natural back-pressure — the producer thread is suspended, reducing load. offer(e, timeout, unit): waits up to timeout, returns false if still full. offer(e): returns false immediately without blocking. Blocking on full queue is the most common producer-consumer design.
Q64. What is a two-lock queue (Michael-Scott queue)?
A: Michael-Scott queue: classic lock-free concurrent FIFO queue using two CAS operations — one for head (dequeue) and one for tail (enqueue). Java's ConcurrentLinkedQueue is based on this algorithm. Allows concurrent enqueue and dequeue without a single global lock. The trick: dummy node at head so enqueue and dequeue never compete for the same node.
Q65. How does JMM handle inter-thread visibility for objects written by multiple threads?
A: If Thread A writes to a field without synchronization and Thread B reads it, B may see any value — stale, partially-constructed, or current. The only guarantees are via happens-before: synchronized (same lock), volatile, Thread.start/join, Class initialization. Without these, behavior is formally undefined (not just "stale" — could be arbitrary values due to reordering).
Q66. What is a cache of type Map<K, SoftReference<V>>?
A: Pattern for memory-sensitive cache: values are SoftReferences — GC clears them when memory is low. Map itself may also use WeakReference for keys (WeakHashMap). Problem: map grows unbounded (stale SoftReference entries). Fix: use ReferenceQueue to clean stale entries, or use Guava Cache/Caffeine which handles this automatically.
Q67. What are ConcurrentHashMap atomic update methods?
A: All atomic: compute(key, BiFunction), computeIfAbsent(key, Function), computeIfPresent(key, BiFunction), merge(key, value, BiFunction), putIfAbsent(key, value), replace(key, value), replace(key, old, new), remove(key, value). These eliminate the need for external synchronization for common map update patterns.
Q68. What is the impact of false sharing on ConcurrentHashMap?
A: CHM's internal counter cells (used for mappingCount()) are padded with @Contended to prevent false sharing — each counter cell is on a separate cache line. Without padding, multiple threads updating adjacent cells would invalidate each other's caches. This is why LongAdder (which CHM uses internally) is more efficient than AtomicLong under contention.
Q69. What is the ideal queue type for a rate limiter?
A: DelayQueue: elements become available at their scheduled time — natural rate limiting with priority to "due" items. SynchronousQueue: direct hand-off — strict one-in-one-out, natural rate limiting. Semaphore: permits control concurrent access count. For production rate limiting: use Resilience4j RateLimiter (token bucket/sliding window) rather than raw concurrent primitives.
Q70. What are the top 5 concurrent collection mistakes in Java?
A: 1) Using HashMap in multi-threaded code (use ConcurrentHashMap). 2) Using ArrayList for concurrent iteration (use CopyOnWriteArrayList or iterate under lock). 3) Synchronized collection with compound operations without external lock. 4) Collections.synchronizedList iteration without synchronized block (CME risk). 5) CopyOnWriteArrayList for large write-heavy lists (O(n) copy per write). 6) Assuming ConcurrentHashMap operations are strongly consistent across multiple calls.
Post a Comment
Add