| synchronized | JVM built-in; auto-release; no tryLock |
| ReentrantLock | tryLock, fairness, interruptible; must unlock in finally |
| ReentrantReadWriteLock | Multiple readers OR one writer; read-heavy optimization |
| StampedLock | Optimistic read; faster than RWL for read-heavy workloads |
| Semaphore(n) | n concurrent permits; rate limiting / resource pools |
| CountDownLatch | One-time barrier; await until count reaches 0 |
| CyclicBarrier | Reusable phase barrier; all parties meet then proceed |
Synchronization Fundamentals
Q1. What are the three aspects of synchronization in Java?
A: 1) Atomicity: operations complete without interference from other threads (compound read-modify-write). 2) Visibility: writes by one thread are seen by other threads (memory model). 3) Ordering: instruction reordering prevented where necessary. synchronized provides all three. volatile provides visibility + limited ordering but NOT atomicity for compound ops.
Q2. What is intrinsic lock (monitor lock)?
A: Every Java object has a built-in intrinsic lock (monitor). synchronized method/block acquires the intrinsic lock of the specified object. Lock is automatically released when the synchronized block exits (normal or exception). Reentrant — same thread can acquire the same lock multiple times without deadlock.
Q3. What is lock striping?
A: Lock striping divides a data structure into N segments, each with its own lock. Threads operating on different segments proceed concurrently. ConcurrentHashMap uses this internally (16 segments in Java 7, per-bucket locking in Java 8). Reduces contention compared to one global lock while maintaining correctness.
// Striped lock pattern:
Object[] locks = new Object[16];
for (int i = 0; i < locks.length; i++) locks[i] = new Object();
void put(int key, int value) {
int stripe = Math.abs(key % locks.length);
synchronized (locks[stripe]) {
data[key] = value; // only blocks threads on same stripe
}
}
Q4. What is lock ordering to prevent deadlock?
A: Always acquire multiple locks in a globally consistent order. If Thread A and B both need locks L1 and L2, both must acquire L1 before L2. If Thread A acquires L1 then L2, while Thread B acquires L2 then L1 — deadlock. Establish a canonical ordering (e.g., by object id, hash code, or name) and document it.
// Safe: always lock lower-id account first
void transfer(Account from, Account to, long amount) {
Account first = from.id < to.id ? from : to;
Account second = from.id < to.id ? to : from;
synchronized(first) {
synchronized(second) {
from.debit(amount); to.credit(amount);
}
}
}
Q5. What is a latch and how does it differ from a gate?
A: Latch (CountDownLatch): starts closed, opens after N events, stays open permanently — one-time operation. Gate: a thread waits until some condition becomes true (boolean flag or similar). CountDownLatch is a latch. CyclicBarrier acts more like a repeatable gate for phased execution. Semaphore(1) acts as a gate/mutex.
Q6. How does ReentrantLock's fairness mode work?
A: ReentrantLock(true) creates a fair lock — threads acquire lock in FIFO order (longest-waiting thread gets it first). Prevents starvation but reduces throughput (no "barging" allowed). ReentrantLock(false) — default, unfair — a newly requesting thread may "barge" ahead of waiting threads — better performance. Fair mode useful when starvation would be catastrophic.
Q7. What is lock barging?
A: Lock barging: a thread requesting a lock acquires it before queued waiting threads — only possible with unfair lock. Improves throughput (less context switching) but can cause starvation for waiting threads. ReentrantLock in unfair mode allows barging; fair mode prevents it. synchronized also allows barging (unfair).
Q8. What is the difference between Condition.await() and Object.wait()?
A: Both: release lock and wait, re-acquire lock on return, must be called while holding the lock, subject to spurious wakeups (use in while loop). Condition advantages: multiple conditions per lock (e.g., notEmpty + notFull), interruptible await, timed await with various time units, awaitUninterruptibly(). Object.wait() is tied to one condition per monitor.
ReentrantLock lock = new ReentrantLock();
Condition notEmpty = lock.newCondition();
Condition notFull = lock.newCondition();
// In producer: while (isFull()) notFull.await();
// After add: notEmpty.signal();
// In consumer: while (isEmpty()) notEmpty.await();
// After remove: notFull.signal();
Q9. What is tryLock() and when should you use it?
A: tryLock(): attempts to acquire lock immediately — returns true if acquired, false if not (no blocking). tryLock(time, unit): waits up to time for lock. Use for: avoiding deadlock (backoff and retry), implementing timeouts, non-blocking alternatives when lock unavailable is acceptable. Always pair with unlock() in finally when tryLock returns true.
if (lock.tryLock(100, TimeUnit.MILLISECONDS)) {
try {
// work
} finally {
lock.unlock();
}
} else {
// could not get lock — take alternative action
}
Q10. What is lockInterruptibly()?
A: lockInterruptibly() acquires the lock but allows the thread to be interrupted while waiting. If interrupted, throws InterruptedException and does not acquire lock. Contrast with lock() which ignores interruption while waiting. Use in cancelable long-running operations that need locks.
Q11. How does StampedLock's optimistic read work?
A: tryOptimisticRead() returns a stamp (long) without acquiring any lock. Thread reads the data. validate(stamp) checks if any write occurred since the stamp was issued. If valid, use the read values. If invalid (write happened), fall back to read lock. Optimistic reads have zero locking overhead — ideal when writes are rare.
StampedLock sl = new StampedLock();
long stamp = sl.tryOptimisticRead();
double x = this.x; double y = this.y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try { x = this.x; y = this.y; }
finally { sl.unlockRead(stamp); }
}
return Math.sqrt(x*x + y*y);
Q12. What are StampedLock limitations?
A: Not reentrant — a thread holding a read lock cannot acquire a write lock (deadlock). Not a Monitor — no wait/notify. Stamps can overflow (extremely rare). Cannot be unlocked with wrong stamp. Complex API makes errors easy. For most applications, ReentrantReadWriteLock is safer; StampedLock only for known read-heavy hot paths.
Q13. What is the difference between acquire() blocking and non-blocking in Semaphore?
A: acquire(): blocks until a permit is available (puts thread in WAITING state). acquire(n): acquires n permits (blocks until all available). tryAcquire(): returns immediately — true if permit available, false otherwise. tryAcquire(time, unit): waits up to time. release() always succeeds (even without prior acquire — can be used as a signal from another thread).
Q14. How is a Semaphore different from a mutex?
A: Mutex (binary semaphore, n=1): mutual exclusion — only one thread at a time, must be released by the same thread that acquired. Semaphore(n): up to n permits simultaneously, can be released by a DIFFERENT thread than the one that acquired. This makes Semaphore useful for producer-consumer signaling (release from producer, acquire from consumer).
Q15. What is a fair Semaphore?
A: Semaphore(n, true) — fair mode — threads acquire permits in FIFO order. Prevents starvation. Semaphore(n, false) — unfair (default) — higher throughput, no ordering guarantee. Same trade-off as ReentrantLock fairness.
Q16. How do you use CountDownLatch for a starting gate?
// All threads ready; release them all simultaneously:
CountDownLatch startGate = new CountDownLatch(1);
CountDownLatch endGate = new CountDownLatch(numWorkers);
for (int i = 0; i < numWorkers; i++) {
new Thread(() -> {
startGate.await(); // all threads wait
try { doWork(); }
finally { endGate.countDown(); }
}).start();
}
startGate.countDown(); // release all at once
endGate.await(); // wait for all to finish
Q17. Can CountDownLatch count up?
A: No — CountDownLatch can only count down (countDown()). If you need to count up and then await, use Phaser: register() increases parties count, arrive() decrements. Alternatively, use AtomicInteger for counting logic with manual wait/notify, or LongAccumulator for accumulation patterns.
Q18. What is CyclicBarrier's barrierAction?
A: CyclicBarrier(parties, Runnable barrierAction) — the Runnable runs once when all parties arrive at the barrier, BEFORE any thread is released. Useful for combining partial results from multiple threads before the next phase. Runs in the thread that triggers the barrier (the last to arrive).
CyclicBarrier barrier = new CyclicBarrier(4, () -> {
// Runs when all 4 arrive, before releasing:
mergePartialResults(); // combine results from 4 threads
});
// Each thread: doPhaseWork(); barrier.await(); doNextPhase();
Q19. What happens if a thread throws an exception in CyclicBarrier.await()?
A: If a thread is interrupted or times out at the barrier, the barrier is "broken" — BrokenBarrierException is thrown to all other waiting threads. The barrier must be reset() to be reused after being broken. CountDownLatch doesn't have this issue (count only goes down).
Q20. What is Phaser and when is it better than CyclicBarrier?
A: Phaser advantages: dynamic registration (parties can register/deregister at runtime), multiple phases with automatic phase advancement, tiered structure for large-scale parallelism. CyclicBarrier: simpler API, parties fixed at construction. Use Phaser when: number of participants changes, need many phases, or hierarchical coordination of large thread groups.
Phaser phaser = new Phaser(1); // register main thread
for (int i = 0; i < 4; i++) {
phaser.register(); // dynamic registration
new Thread(() -> {
doWork(); phaser.arriveAndAwaitAdvance(); // phase 1
doMoreWork(); phaser.arriveAndDeregister(); // phase 2, then deregister
}).start();
}
phaser.arriveAndAwaitAdvance(); // main waits for phase 1
phaser.arriveAndDeregister(); // main done
Q21. What are the Phaser arrive methods?
A: arrive(): signals arrival but does NOT wait — immediate return. arriveAndAwaitAdvance(): signals and waits for all parties (equivalent to barrier). arriveAndDeregister(): signals and removes self from future phases. onAdvance(phase, registeredParties): overridable hook called when all parties arrive — return true to terminate Phaser.
Q22. What is Exchanger and its typical use case?
A: Exchanger<V> synchronizes exactly two threads at a rendezvous point to exchange objects. exchange(item) blocks until the other thread also calls exchange — then both receive each other's item. Use: pipeline stages where a producer fills a buffer and a consumer takes it at the same time (double-buffering pattern for throughput).
Exchanger<List<String>> exchanger = new Exchanger<>();
// Producer:
List<String> filledBuffer = new ArrayList<>();
filledBuffer.add(produceData());
List<String> emptyBuffer = exchanger.exchange(filledBuffer);
// Consumer:
List<String> dataBuffer = exchanger.exchange(new ArrayList<>());
consume(dataBuffer);
Q23. What are Atomic classes in java.util.concurrent.atomic?
A: Atomic classes provide lock-free thread-safe operations using CAS (compare-and-swap): AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference<V>, AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray, AtomicStampedReference, AtomicMarkableReference. Also Java 8 adders: LongAdder, LongAccumulator, DoubleAdder, DoubleAccumulator.
Q24. What is the difference between AtomicLong and LongAdder?
A: AtomicLong: single CAS location — high contention causes CAS retries (spinning). get() is always current value. LongAdder: multiple internal cells; threads update different cells (stripes) to reduce CAS contention. sum() aggregates all cells. LongAdder: better write throughput under high contention. AtomicLong: better when reads must always see latest value atomically.
Q25. What is LongAccumulator?
A: LongAccumulator (Java 8) is a generalization of LongAdder. Takes a LongBinaryOperator and identity value. accumulate(value) applies the function to current accumulated value and the input. Examples: max tracker (accumulate with Math::max), min tracker, sum (same as LongAdder). get() returns the accumulated value. Thread-safe without locks.
LongAccumulator maxAccumulator = new LongAccumulator(Math::max, Long.MIN_VALUE);
maxAccumulator.accumulate(5);
maxAccumulator.accumulate(10);
maxAccumulator.accumulate(3);
System.out.println(maxAccumulator.get()); // 10
Q26. What is AtomicStampedReference used for?
A: AtomicStampedReference solves the ABA problem in CAS. Stores a reference + integer stamp. compareAndSet requires both reference AND stamp to match expected values. Callers increment stamp on each successful update — a cycle A→B→A also increments stamp, so the old stamp no longer matches. AtomicMarkableReference uses a boolean mark instead of integer stamp.
AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
int[] stamp = {0};
String current = ref.get(stamp); // get value and stamp
ref.compareAndSet(current, "B", stamp[0], stamp[0] + 1); // CAS with stamp
Q27. What is VarHandle (Java 9) and how does it relate to Atomic classes?
A: VarHandle provides low-level access to field operations with specified memory ordering (getVolatile, setVolatile, getAcquire, setRelease, compareAndSet). More flexible and performant than Atomic wrappers for high-performance lock-free code. Most application code should use AtomicXxx; VarHandle is for framework/library authors replacing Unsafe. Obtained via MethodHandles.lookup().findVarHandle().
Q28. What is the compareAndExchange() method added in Java 9?
A: compareAndExchange(expectedValue, newValue) — like compareAndSet but returns the WITNESS value (the actual current value at the time of the operation). If CAS succeeds, witness == expected. If fails, witness tells you what the current value is — eliminates the need for a separate get() call in retry loops. Available on AtomicInteger, AtomicLong, VarHandle.
Q29. What is the Unsafe class and why is it dangerous?
A: sun.misc.Unsafe provides low-level operations: direct memory allocation (allocateMemory), off-heap access, CAS on arbitrary memory offsets, park/unpark, monitor operations. Used internally by JDK for Atomic classes, LockSupport, NIO. Dangerous: no bounds checking, no GC tracking, can crash JVM. Since Java 9, access increasingly restricted; VarHandle and Foreign Memory API are safe alternatives.
Q30. What are the memory ordering modes in VarHandle?
A: Plain: no ordering guarantees (fastest). Opaque: no reordering relative to other opaque/volatile accesses on same variable. Acquire: reads see all writes before a release. Release: writes visible to acquire reads. Volatile: full happens-before (same as volatile field). Full-fence ordering modes enable fine-grained performance optimization without unsafe code.
Q31. What is a spin wait hint (Thread.onSpinWait(), Java 9)?
A: Thread.onSpinWait() is a hint to the CPU that the current thread is in a spin-wait loop. On x86, maps to PAUSE instruction — reduces power consumption and improves throughput in spin loops by preventing speculative execution pipeline hazards. Use in custom spin locks or polling loops.
while (!condition) {
Thread.onSpinWait(); // x86 PAUSE hint
}
Q32. What is the ReadWriteLock upgrade/downgrade limitation?
A: ReentrantReadWriteLock does NOT support lock upgrade: a thread holding a read lock CANNOT acquire a write lock (would deadlock since write lock waits for all readers to finish, including itself). Lock downgrade IS supported: hold write lock, acquire read lock, release write lock. Use StampedLock.tryConvertToWriteLock() for upgrade pattern.
// Downgrade: write -> read (safe):
writeLock.lock();
try {
updateData();
readLock.lock(); // acquire read while holding write
} finally {
writeLock.unlock(); // downgrade: release write, keep read
}
try { readData(); } finally { readLock.unlock(); }
Q33. How do you implement a bounded buffer (blocking queue) with Lock + Condition?
class BoundedBuffer<T> {
private final LinkedList<T> buffer = new LinkedList<>();
private final int capacity;
private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
void put(T item) throws InterruptedException {
lock.lock();
try {
while (buffer.size() == capacity) notFull.await();
buffer.add(item);
notEmpty.signal();
} finally { lock.unlock(); }
}
T take() throws InterruptedException {
lock.lock();
try {
while (buffer.isEmpty()) notEmpty.await();
T item = buffer.poll();
notFull.signal();
return item;
} finally { lock.unlock(); }
}
}
Q34. What is the Condition.awaitNanos() and awaitUntil()?
A: awaitNanos(nanosTimeout): waits up to nanoseconds, returns remaining time (positive if expired naturally, 0 or negative if timed out). awaitUntil(Date): waits until absolute Date — returns true if signalled, false if deadline passed. awaitUninterruptibly(): waits without being interruptible — never throws InterruptedException (sets interrupt flag on return).
Q35. What is the lock coarsening optimization?
A: JIT compiler can merge adjacent synchronized blocks on the same object into a single larger block (coarsening) to reduce lock acquisition/release overhead. Example: three consecutive synchronized(obj){} blocks become one. Developers can also manually coarsen if the unsynchronized work between blocks is trivial — but be careful not to hold lock during I/O or slow operations.
Q36. What is lock elision?
A: JIT can remove (elide) a synchronized block if escape analysis proves the lock object doesn't escape the current thread — no other thread can access it, so the lock is unnecessary. Common: synchronizing on new objects in tight loops. Developers shouldn't rely on this but should know it exists as a JVM optimization.
Q37. How does synchronized handle inheritance?
A: synchronized is not inherited — a subclass overriding a synchronized method gets an unsynchronized method unless it adds synchronized explicitly. Calling super.synchronizedMethod() from an unsynchronized override uses the superclass synchronized code but the method call itself is not synchronized. Always explicitly mark overrides as synchronized when needed.
Q38. What is double-checked locking and why does it need volatile?
A: Double-checked locking for lazy singleton: without volatile, the JVM may: 1) allocate memory, 2) assign reference (publishing partially-constructed object), 3) run constructor — in that order. Another thread seeing non-null reference in first check would use a partially-constructed singleton. volatile prevents this reordering (full memory barrier on write). Safe in Java 5+ with volatile.
Q39. What is publication and safe publication?
A: Publication: making an object available to other threads. Safe publication ensures all threads see a fully constructed object. Safe publication mechanisms: 1) static initializer (JVM guarantees). 2) volatile field assignment. 3) final fields (object fully constructed before reference visible). 4) synchronized publication. Unsafe: assigning to non-volatile, non-final field without synchronization.
Q40. What is an immutable object and why is it thread-safe?
A: Immutable object: state cannot change after construction. Thread-safe without synchronization — any number of threads can read it concurrently. Requirements: all fields final, no mutable objects shared (defensive copy arrays), no "this" escape in constructor. Examples: String, Integer, LocalDate, records. Favor immutability for shared state — simplest concurrency model.
Q41. What is the "this" reference escape problem?
A: "This" escape: publishing the "this" reference before the constructor finishes — other threads may see a partially constructed object. Common pitfalls: starting a thread in a constructor (thread may run before constructor completes), registering listeners in constructor, calling overridable methods in constructor. Fix: use static factory methods, finish construction before publishing.
// Unsafe: this escapes during construction
class Unsafe {
Unsafe(EventBus bus) {
bus.register(this); // 'this' escapes before constructor done!
}
}
// Safe: static factory
class Safe {
static Safe create(EventBus bus) {
Safe s = new Safe();
bus.register(s); // fully constructed
return s;
}
}
Q42. What is effective immutability?
A: An object that is mutable by design but whose state is never changed after safe publication. Example: a List populated during construction and then never modified — it's not immutable (List has mutation methods) but effectively immutable for concurrent reads. No synchronization needed for reads as long as no mutations happen. Document the effective immutability contract clearly.
Q43. What is a ThreadSafe annotation?
A: @ThreadSafe (JCIP annotations): documents that a class is thread-safe. @NotThreadSafe: explicitly not thread-safe. @GuardedBy("lockName"): field/method must be accessed only with the named lock held. These annotations don't enforce anything at runtime but serve as documentation and can be checked by static analysis tools (SpotBugs).
Q44. What is the condition predicate pattern?
A: The condition predicate is the test a thread must pass before proceeding (e.g., buffer not empty). Always check condition predicate in a while loop (not if) to handle spurious wakeups and missed signals. The condition predicate must be re-evaluated each time the thread re-acquires the lock after waiting.
// Pattern: while (!conditionPredicate()) { lock.wait(); }
synchronized void waitForData() throws InterruptedException {
while (buffer.isEmpty()) { // NOT if — handles spurious wakeups
wait();
}
// safe to use buffer here
}
Q45. What is missed signal in wait/notify?
A: Missed signal: notify() called BEFORE the other thread calls wait() — the notification is missed. The waiting thread waits forever. Prevented by: always check condition BEFORE waiting (while loop pattern), hold lock when modifying condition AND when calling notify. CountDownLatch and Semaphore don't have this issue (state persists after notification).
Q46. What is the difference between signaling vs broadcasting?
A: signal()/notify(): wake ONE arbitrary waiting thread. signalAll()/notifyAll(): wake ALL waiting threads. Use notifyAll/signalAll when: multiple threads waiting on different conditions but using same monitor, or when in doubt. Use signal/notify only when: exactly one waiting thread can proceed (single consumer), all waiting threads are identical. Wrong use of signal can cause missed notifications.
Q47. What is monitor ownership and what happens with illegal monitor state?
A: To call wait()/notify()/notifyAll(), the current thread must OWN the object's monitor (be inside synchronized block on same object). Calling them without ownership throws IllegalMonitorStateException. Same for Condition methods — must hold the associated Lock. Common mistake: calling obj.wait() from a thread that doesn't hold obj's monitor.
Q48. What is the impact of synchronization on CPU caches?
A: When synchronized block exits (unlock), the JVM flushes the CPU write buffer to main memory (store fence). When entering synchronized block (lock), the JVM invalidates CPU cache (load fence) — next reads go to main memory. This is how synchronized provides visibility. volatile does the same with individual variable reads/writes.
Q49. How does Java prevent race conditions in class initialization?
A: JVM guarantees class initialization is thread-safe. If two threads trigger loading of the same class simultaneously, one executes the class initializer while the other waits. Static fields are initialized before any thread can use them. This is the basis for the initialization-on-demand holder singleton pattern — Class loading is a free synchronization mechanism.
Q50. What is a lock-free data structure?
A: Lock-free: uses CAS operations instead of locks — a thread failing CAS retries without blocking. At least one thread always makes progress (no deadlock). Wait-free (stronger): EVERY thread makes progress in bounded steps. Java examples: ConcurrentLinkedQueue, AtomicXxx. Lock-free trades simplicity for performance in specific high-contention scenarios.
Q51. What is compare-and-swap in AtomicInteger.updateAndGet()?
AtomicInteger val = new AtomicInteger(5);
// updateAndGet(IntUnaryOperator):
int result = val.updateAndGet(n -> n * 2); // 10 — atomically reads and updates
// accumulateAndGet(update, IntBinaryOperator):
int result2 = val.accumulateAndGet(3, Integer::max); // max(10, 3) = 10
// Internal loop (how it works):
// int current = get();
// int next = operator.apply(current);
// while(!compareAndSet(current, next)) {
// current = get(); next = operator.apply(current);
// }
Q52. What is a non-blocking algorithm?
A: Non-blocking: a thread's failure or suspension doesn't prevent other threads from progressing. CAS-based algorithms are non-blocking (failed CAS retries, doesn't block). Contrast with blocking: thread waiting for a lock is blocked — if lock holder is slow/dead, all waiters are stuck. Non-blocking algorithms are more resilient but harder to implement correctly.
Q53. What is the Amdahl's Law implication for parallel code?
A: Amdahl's Law: speedup limited by serial fraction. If 10% of code is serial, max speedup = 1/(0.1) = 10x regardless of how many cores. Implication: reduce lock-protected serial sections as much as possible. Use fine-grained locking, concurrent collections, lock-free algorithms. Perfect parallelism doesn't exist — focus on minimizing the serial bottleneck.
Q54. What is a condition variable spurious wakeup?
A: Spurious wakeup: a thread wakes up from wait()/await() without being notified — JVM/OS artifact. POSIX threads specification explicitly allows this. Solution: always check condition predicate in while loop after waking. Never assume wakeup = condition satisfied. This is why "while(!condition) wait()" is the canonical pattern.
Q55. What concurrent collections support concurrent iteration without locking?
A: CopyOnWriteArrayList: iterator reflects snapshot at creation time — no ConcurrentModificationException, but sees stale data, expensive writes. ConcurrentHashMap: weakly-consistent iterators — may or may not reflect concurrent updates, no CME. ConcurrentLinkedQueue: iterator weakly-consistent. These provide concurrent iteration at the cost of absolute consistency guarantees.
Q56. What is the "wait on the right object" mistake?
A: A common bug: calling lock.wait() on a different object than the one used for synchronization, or calling notify() on a different object than the one threads wait() on. The thread notified is waiting on a different monitor — no effect. Always use the same object for both synchronized and wait/notify. Use Condition objects from the same ReentrantLock to avoid this.
Q57. How do you implement a reader-writer lock preference?
A: ReentrantReadWriteLock has a "write preference" in unfair mode: if a write is waiting, new reader requests queue behind it (prevents writer starvation). In fair mode: strict FIFO. StampedLock: readers never block writers (optimistic) until a write actually happens. Choose based on your read/write ratio and starvation tolerance.
Q58. What is the ReadWriteLock performance advantage over synchronized?
A: For read-heavy workloads: synchronized allows only one reader at a time. ReadWriteLock allows unlimited concurrent readers (as long as no writer). Example: 100 threads reading a cache, 1 thread updating. With synchronized: 1 concurrent reader. With RWL: 99 concurrent readers + 1 writer (serialized). Significant throughput improvement for read ratios > 80%.
Q59. What is the difference between Semaphore acquire and AtomicInteger decrementAndGet?
A: Semaphore.acquire(): BLOCKS if count is 0 — puts thread in WAITING state until a permit is available. AtomicInteger.decrementAndGet(): NEVER blocks — always returns immediately, even if result is negative. Semaphore provides waiting/blocking semantics; AtomicInteger provides atomic arithmetic without blocking. Use Semaphore for resource limits with waiting; Atomic for counters without waiting.
Q60. What is a concurrent bag vs concurrent set?
A: Concurrent bag: allows duplicates (multi-set) — ConcurrentLinkedDeque can be used as a bag. Concurrent set: unique elements — ConcurrentSkipListSet (sorted, thread-safe), Collections.newSetFromMap(new ConcurrentHashMap()) (unsorted). Java 21 SequencedSet interface adds ordering semantics to sets.
Q61. What is the atomic field updater (AtomicReferenceFieldUpdater)?
A: AtomicReferenceFieldUpdater<T,V> performs CAS on a volatile field of an existing object without wrapping the field in AtomicReference. Useful when you don't control the class or want to minimize object creation (one updater shared across all instances). Also: AtomicIntegerFieldUpdater, AtomicLongFieldUpdater.
class Node {
volatile Node next; // must be volatile
}
AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
nextUpdater.compareAndSet(node, expectedNext, newNext);
Q62. What is a happens-before chain?
A: Happens-before is transitive: if A happens-before B, and B happens-before C, then A happens-before C. Building chains: Thread A unlocks → Thread B locks (HB) → Thread B's volatile write → Thread C's volatile read. All of A's writes before unlock are visible to C after its volatile read. Understanding transitivity is key to reasoning about complex concurrent code.
Q63. What is a memory fence (memory barrier)?
A: Hardware instruction preventing reordering of memory operations. Types: LoadLoad (no load reordered past another load), StoreStore (no store reordered past another store), LoadStore, StoreLoad (strongest — full fence). volatile write = StoreLoad fence. synchronized unlock = StoreLoad fence. Understanding fences explains why volatile/synchronized prevents visibility issues.
Q64. What is the Java concurrency utils package overview?
A: java.util.concurrent (JUC): Executors and thread pools, concurrent collections (ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueues), synchronizers (CountDownLatch, CyclicBarrier, Semaphore, Phaser, Exchanger), atomic classes, locks (ReentrantLock, ReadWriteLock, StampedLock), ForkJoin framework, CompletableFuture. Introduced Java 5 (JSR-166). java.util.concurrent.locks: lock interfaces. java.util.concurrent.atomic: atomic operations.
Q65. How does synchronized interact with the JIT compiler?
A: JIT optimizes synchronized: 1) Biased locking (removed Java 18): if only one thread acquires, marks object with thread ID — subsequent acquires need no CAS. 2) Thin lock: fast CAS-based lock for uncontended case. 3) Inflated (heavy) lock: OS mutex for contended case. 4) Lock elision: removes unnecessary locks via escape analysis. 5) Lock coarsening: merges adjacent synchronized blocks.
Q66. What is contention and how do you reduce it?
A: Contention: multiple threads competing for the same lock — one runs, others block. High contention = poor scalability. Reduction strategies: 1) Reduce scope of critical sections (hold lock less time). 2) Lock striping (multiple locks). 3) Lock-free algorithms. 4) Immutable data. 5) Thread-local copies. 6) Read-write locks for read-heavy scenarios. 7) Separate hot and cold data.
Q67. What is split lock and cache ping-pong?
A: Cache ping-pong: multiple cores repeatedly invalidate each other's caches for the same cache line. Happens with CAS-heavy AtomicInteger under high contention (Intel CPUs). LongAdder mitigates by spreading updates across multiple cells. @Contended annotation (jdk.internal) pads variables to put them on separate cache lines. Performance-critical concurrent code needs CPU cache awareness.
Q68. What is safe publication using final fields?
A: JMM guarantees: once an object's constructor completes, all threads see the final fields' initialized values — no synchronization needed. This is why immutable objects (all final fields) are automatically thread-safe to publish. The reference itself must still be published safely (volatile/synchronized) but once other threads see the reference, they see all final field values.
Q69. How do you implement a thread-safe cache with lazy initialization?
// ConcurrentHashMap.computeIfAbsent (atomic, no double-init):
ConcurrentHashMap<String, Data> cache = new ConcurrentHashMap<>();
Data getData(String key) {
return cache.computeIfAbsent(key, k -> loadFromDb(k)); // atomic
}
// computeIfAbsent guarantees the value function is called at most once per key
// (in Java 8+, other threads may also call if computation takes long — fixed in Java 9+)
Q70. What is the best concurrency model for different use cases?
| Use Case | Recommended Approach |
|---|---|
| Simple counter | AtomicLong or LongAdder (high-contention) |
| Shared cache | ConcurrentHashMap + computeIfAbsent |
| Producer-consumer | ArrayBlockingQueue / LinkedBlockingQueue |
| Read-heavy config | ReentrantReadWriteLock or volatile (if single write) |
| Wait for N events | CountDownLatch |
| Limit concurrency | Semaphore or bounded thread pool |
Q71. What is the AbstractQueuedSynchronizer (AQS)?
A: AQS is the framework underlying ReentrantLock, Semaphore, CountDownLatch, CyclicBarrier. Provides a FIFO queue of waiting threads and an int state. Subclass implements tryAcquire/tryRelease (for exclusive locks) or tryAcquireShared/tryReleaseShared (for shared locks). AQS handles queuing, parking/unparking, interruption. Understanding AQS explains how all JUC synchronizers work.
Q72. How does ReentrantLock implement reentrancy?
A: ReentrantLock maintains an owner thread reference and a hold count. On lock(): if no owner, set owner = current thread, count=1. If owner == current thread (reentrant), increment count. On unlock(): decrement count; if count=0, clear owner and wake waiting thread. This is exactly what JVM does for intrinsic locks.
Q73. What are the performance characteristics of different synchronization primitives?
A: Fastest to slowest (uncontended): 1) Atomic ops (CAS) — hardware instruction, ~5ns. 2) synchronized (uncontended) — biased/thin lock, ~10-20ns. 3) ReentrantLock (uncontended) — CAS + JVM bookkeeping, ~20-40ns. 4) synchronized (contended) — OS mutex, ~1-10 microseconds. 5) Context switch — OS scheduling, ~5-10 microseconds. In practice: uncontended synchronization is very fast; avoid contention rather than switching primitives.
Q74. What is the difference between CountDownLatch and Phaser for one-time events?
A: For a simple one-time event (countdown from N to 0): CountDownLatch is simpler and more efficient. Phaser is more complex and has overhead from dynamic registration management. Use Phaser only when you need: dynamic party count, multiple phases, or the onAdvance() termination hook. Otherwise CountDownLatch is the right tool.
Q75. What is the key advice for concurrent code in interviews?
A: 1) Default to immutability. 2) Confine mutable state to single thread when possible. 3) Synchronize ALL accesses to mutable shared state. 4) Hold locks briefly — do I/O outside synchronized blocks. 5) Prefer higher-level concurrent utilities over raw synchronized. 6) Use volatile for single flags, AtomicXxx for single variables, synchronized for compound actions. 7) Test with multiple threads and stress tests — single-threaded tests rarely expose races. 8) Document thread-safety guarantees with @ThreadSafe/@GuardedBy.
Post a Comment
Add