Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
Chaturmind
← Java Interview Prep: 8+ Years (Senior & Lead)

Expert Core Java

  • Tricky Java Output, Operators & OOP Edge Cases — Interview Questions
  • Tricky Exceptions, Memory & Keyword Questions — Interview Questions
  • Classic Java Language Questions, Senior-Grade Answers — Interview Questions
  • Classic Collections, Threads & JDK APIs, Senior-Grade Answers — Interview Questions
  • Reflection, Dynamic Proxies, final & Modern OOP Design — Interview Questions

JVM Internals & Performance

  • Class Loading, Bytecode & Object Layout — Interview Questions
  • JIT Compilation & Runtime Optimisations — Interview Questions
  • Garbage Collectors Deep Dive — Interview Questions
  • JVM Tuning, GC Logs & Memory Footprint — Interview Questions
  • Memory Leaks, OutOfMemoryErrors & Profiling Tools — Interview Questions
  • Modules, Agents & Advanced JVM APIs — Interview Questions

Collections & Concurrency at Scale

  • Collections Internals & Complexity — Interview Questions
  • Iterators, Comparators & Ordering Contracts — Interview Questions
  • Concurrent Collections, Queues & Lock-Free Structures — Interview Questions
  • Threads, Executors & ForkJoin Internals — Interview Questions
  • Locks, Atomics, CAS & Synchronizers — Interview Questions
  • Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions
  • Deadlock, Livelock, Starvation & Concurrent Design — Interview Questions
  • CompletableFuture, Parallel Streams & Non-Blocking I/O — Interview Questions

Modern Java (8 to 21+)

  • Lambdas & Functional Interfaces Internals — Interview Questions
  • Streams & Collectors Deep Dive — Interview Questions
  • Optional & Interface Default/Static Methods — Interview Questions
  • Java 9–25 Features & Virtual Threads — Interview Questions

Design Patterns, SOLID & Clean Code

  • Design Pattern Trade-offs & Combinations — Interview Questions
  • SOLID, Clean Code & Anti-Patterns — Interview Questions

Spring & Spring Boot Internals

  • IoC, Dependency Injection & Bean Lifecycle Internals — Interview Questions
  • Spring AOP, Proxies & @Async Internals — Interview Questions
  • Spring Configuration, Auto-Configuration & Custom Starters — Interview Questions
  • Spring MVC & REST Internals, Exception Frameworks — Interview Questions
  • Spring Security Advanced Internals — Interview Questions
  • Spring WebFlux, Reactor & R2DBC — Interview Questions
  • Spring Cloud, Observability & Distributed Tracing — Interview Questions
  • Spring Boot 3, Native Images & Production Scenarios — Interview Questions

JPA, Hibernate & Databases at Scale

  • Spring Data JPA — Queries, Projections, Custom Repositories & Locking — Interview Questions
  • JPA Entity Mapping, Associations & Cascades — Interview Questions
  • JPQL vs Native Queries in Depth — Interview Questions
  • Hibernate Caching — First-Level, Second-Level & Query Cache — Interview Questions
  • Lazy vs Eager Loading, LazyInitializationException & N+1 — Interview Questions
  • JPA Transactions, Propagation, Isolation & Dirty Checking — Interview Questions
  • SQL vs NoSQL, Indexing & Query Tuning — Interview Questions
  • Database Scaling, Replication, Pooling & Consistency Models — Interview Questions
  • Redis, Search, Time-Series, CDC & Transactional Data Modelling — Interview Questions

Testing Strategy & API Design

  • Spring Boot Test Slices, Context & Test Strategy — Interview Questions
  • Testing Web, Persistence, Security, Async & Messaging in Spring Boot — Interview Questions
  • JUnit 5 & Mockito, Advanced — Interview Questions
HomeLearnJava Interview PrepJava Interview Prep: 8+ Years (Senior & Lead)Collections & Concurrency at Scale
✓ FreeAdvanced· 15 min read

Locks, Atomics, CAS & Synchronizers — Interview Questions

tryLock for deadlock avoidance, reentrancy, limits of synchronized, AtomicInteger vs synchronized, AtomicReference vs volatile, read/write locks and ReentrantReadWriteLock, StampedLock vs ReentrantLock, CAS and lock-free/non-blocking algorithms, busy spinning, LongAdder vs AtomicLong, CountDownLatch vs CyclicBarrier vs Semaphore, Phaser and Exchanger, thread affinity, a thread-safe singleton without synchronization, and refactoring unsafe code with synchronization.

Published September 25, 2026


How to use this lesson

For each tool, say what problem it solves and what it costs:

  • intrinsic locks vs explicit locks;
  • blocking vs CAS;
  • a single counter vs striped counters.

Senior answers mention contention, fairness, reentrancy and failure modes (lost signals, starvation, ABA).

Q1. How does ReentrantLock.tryLock() help avoid deadlock?

Short answer: A deadlock needs threads that wait forever while holding locks. tryLock() (immediately) or tryLock(timeout, unit) lets a thread give up if it can't get the second lock. It releases what it holds, backs off (with some random jitter), and retries, or fails the operation. That breaks the "hold and wait" condition.

boolean transfer(Account from, Account to, BigDecimal amount) throws InterruptedException {
    long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(1);
    while (System.nanoTime() < deadline) {
        if (from.lock.tryLock()) {
            try {
                if (to.lock.tryLock()) {
                    try { from.debit(amount); to.credit(amount); return true; }
                    finally { to.lock.unlock(); }
                }
            } finally { from.lock.unlock(); }
        }
        Thread.sleep(ThreadLocalRandom.current().nextInt(1, 10));   // random back-off avoids livelock
    }
    return false;                                                     // report a timeout instead of hanging
}

Key points to cover:

  • The simpler, stronger fix is often a global lock ordering (always lock the lower account ID first).

Learn it in depth → synchronized and Locks

Q2. What's the difference between a Semaphore and a CountDownLatch? What is a Semaphore? What is a CountDownLatch?

Short answer:

  • Semaphore(n): holds n permits. acquire() takes one, blocking if none are left, and release() returns one. It limits concurrent access to a resource: a connection limit, a bulkhead, rate-limiting parallel calls. It's reusable, and any thread may release a permit (no ownership).
  • CountDownLatch(n): a one-shot gate. Threads await() until the count reaches zero, through countDown() calls from other threads. It can't be reset. Uses: wait for N services to initialise, start N workers simultaneously (a latch of 1 as a "starting gun"), or wait for N tasks to finish.
Semaphore maxConcurrentCalls = new Semaphore(20);
<T> T callWithLimit(Callable<T> call) throws Exception {
    maxConcurrentCalls.acquire();
    try { return call.call(); } finally { maxConcurrentCalls.release(); }
}

Learn it in depth → Concurrent Utilities & Coordination

Q3. How does CyclicBarrier differ from CountDownLatch? When would you use each?

Short answer:

CountDownLatchCyclicBarrier
Who waitsAny threads (the waiters and the counters can be different)The participating threads wait for each other
TriggercountDown() calls reach zeroawait() called by all N parties
ReusableNo (one-shot)Yes (it resets after each trip)
Extra—An optional barrier action runs when the barrier trips. A failure breaks it for everyone
  • Latch: "start the API only after the cache, database and broker are ready", or "the main thread waits for 10 download tasks".
  • Barrier: "each simulation step must finish on every worker before any starts the next step". Use Phaser when the number of parties changes.

Q4. Can a lock be acquired recursively with synchronized and with ReentrantLock?

Short answer: Yes, both are reentrant. A thread that holds the lock can acquire it again: a synchronized method can call another synchronized method on the same object. The lock keeps a hold count, and is only released when the count returns to zero. For ReentrantLock, every lock() needs a matching unlock() (getHoldCount() shows the depth). Reentrancy prevents self-deadlock in call chains, and in overriding methods that call super.

Common trap: StampedLock is not reentrant. Acquiring its write lock twice from the same thread deadlocks (Q9).

Q5. What are the disadvantages of using only synchronized blocks?

Short answer: Intrinsic locks are simple and well optimised, but they can't:

  • time out or try to acquire (tryLock);
  • be interrupted while waiting;
  • be fair (no FIFO option);
  • provide several condition queues (there's only one wait set per monitor, which forces notifyAll);
  • be acquired in one method and released in another (they're block-structured);
  • separate read and write access.

Other issues:

  • It's easy to over-lock (holding locks during I/O).
  • Contention is hard to observe.
  • In Java 21–23, blocking inside synchronized pinned virtual threads (fixed in Java 24).

On the positive side: it's impossible to forget the unlock, and there's less boilerplate.

Q6. When should you prefer AtomicInteger over synchronized?

Short answer: For single-variable updates: counters, sequence numbers, flags, compare-and-set state transitions. incrementAndGet(), getAndUpdate() and compareAndSet() are lock-free (hardware CAS), with no blocking or context switches, and they scale well under moderate contention.

Use synchronized/locks when an invariant spans several variables (for example, balance and transactionCount must change together), or for check-then-act logic over collections. For heavily contended counters, prefer LongAdder (Q13).

Q7. What's the difference between an AtomicReference and a volatile reference?

Short answer:

  • A volatile reference gives visibility and ordering: reads see the latest write. But a read-modify-write (for example "replace this if it's still that") isn't atomic, and two threads can overwrite each other.
  • AtomicReference adds atomic compound operations: compareAndSet(expected, new), getAndSet, updateAndGet(fn), accumulateAndGet. That enables lock-free, copy-on-write updates of immutable state.
private final AtomicReference<Config> config = new AtomicReference<>(Config.defaults());
void enableFeature(String flag) {
    config.updateAndGet(c -> c.withFlag(flag, true));     // CAS retry loop; the function must be side-effect free
}

Use volatile for "publish a new value, and everyone sees it". Use AtomicReference when the new value depends on the old one.

Q8. How do you implement a read/write lock scenario? How does ReentrantReadWriteLock differ from a standard lock?

Short answer: A ReentrantReadWriteLock has two locks:

  • a shared read lock (many readers at once);
  • an exclusive write lock (it blocks both readers and writers).

It's a good fit for read-mostly data where reads are non-trivial, and writes are rare.

private final ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private final Map<String, Rate> rates = new HashMap<>();

Rate get(String ccy) {
    rw.readLock().lock();
    try { return rates.get(ccy); } finally { rw.readLock().unlock(); }
}
void refresh(Map<String, Rate> fresh) {
    rw.writeLock().lock();
    try { rates.clear(); rates.putAll(fresh); } finally { rw.writeLock().unlock(); }
}

Differences from a plain ReentrantLock:

  • Concurrency for readers.
  • Downgrading (write → read) is allowed.
  • Upgrading (read → write) deadlocks.
  • Fair or non-fair modes, and writer starvation is possible under constant reads (non-fair).
  • The overhead is higher than a simple lock, so for tiny critical sections or balanced workloads it can be slower.

Often, an immutable snapshot in a volatile field or ConcurrentHashMap beats read/write locks entirely.

Q9. What's the difference between StampedLock and ReentrantLock? What is StampedLock?

Short answer: StampedLock (Java 8) offers three modes, each returning a long stamp:

  • write (exclusive);
  • read (shared);
  • optimistic read: no locking at all. You read the fields, then validate(stamp). If a write happened in between, you retry with a real read lock.

Optimistic reads make it the fastest option for read-heavy, short reads.

The caveats:

  • It's not reentrant (re-locking deadlocks).
  • It has no Conditions, and isn't Lock or owner-aware.
  • Its locks aren't interruptible by default (there are *Interruptibly variants).
  • It's easier to misuse: optimistic reads must copy fields into locals, and validate before using them.

ReentrantLock is general-purpose: reentrant, with conditions, fairness, and interruptible acquisition.

double distanceFromOrigin() {                          // the canonical optimistic-read pattern
    long stamp = sl.tryOptimisticRead();
    double cx = x, cy = y;
    if (!sl.validate(stamp)) {                          // a write intervened: fall back to a read lock
        stamp = sl.readLock();
        try { cx = x; cy = y; } finally { sl.unlockRead(stamp); }
    }
    return Math.hypot(cx, cy);
}

Q10. What is CAS (compare-and-swap)?

Short answer: An atomic CPU instruction (x86 LOCK CMPXCHG; ARM LL/SC or LSE CAS): "if memory location V still holds the expected value A, set it to B, and report whether that succeeded". Java exposes it through Atomic*.compareAndSet, VarHandle.compareAndSet, and internally Unsafe. Lock-free algorithms use CAS retry loops: read, compute, CAS, and retry on failure.

Key points to cover:

  • The ABA problem: the value changes from A to B and back to A, so the CAS succeeds incorrectly. Fix it with versioned references (AtomicStampedReference) or garbage-collected nodes.
  • High contention means many retries, which wastes CPU. LongAdder spreads the updates.

Q11. What is lock-free programming? How do non-blocking algorithms differ from blocking ones? How do you design a lock-free algorithm?

Short answer:

  • Blocking algorithms use locks. A thread holding a lock can be descheduled, and everyone else waits. They risk deadlock and priority inversion.

  • Non-blocking algorithms use atomic operations (CAS), so a stalled thread can't stop the others:

    • obstruction-free: a thread finishes if it runs alone;
    • lock-free: some thread always makes progress;
    • wait-free: every thread finishes in bounded steps.

    They're used in ConcurrentLinkedQueue (Michael-Scott queue), ConcurrentSkipListMap, the Atomic* classes, and parts of ConcurrentHashMap.

  • Designing one:

    1. Keep the shared state in one atomically updatable reference (or a few CAS-able fields).
    2. Use read → compute a new immutable state → CAS → retry loops.
    3. Make every intermediate state consistent, so other threads can help complete half-finished operations.
    4. Handle ABA (stamps, or immutable nodes).
    5. Use proper memory ordering (VarHandle acquire/release semantics).
    6. Prove it, and stress test it (jcstress).

Prefer existing JDK structures. Hand-written lock-free code is notoriously hard to get right.

public final class LockFreeStack<T> {                       // Treiber stack
    private record Node<T>(T value, Node<T> next) {}
    private final AtomicReference<Node<T>> head = new AtomicReference<>();
    public void push(T v) {
        Node<T> oldHead, newHead;
        do { oldHead = head.get(); newHead = new Node<>(v, oldHead); }
        while (!head.compareAndSet(oldHead, newHead));
    }
    public T pop() {
        Node<T> oldHead;
        do { oldHead = head.get(); if (oldHead == null) return null; }
        while (!head.compareAndSet(oldHead, oldHead.next()));
        return oldHead.value();
    }
}

Q12. What is "busy spinning"?

Short answer: A thread loops continuously, checking a condition, instead of blocking (while (!ready) { Thread.onSpinWait(); }).

  • Pros: it reacts instantly (nanoseconds), with no context switch or OS wake-up latency. Used in ultra-low-latency systems (trading, the Disruptor's BusySpinWaitStrategy), and briefly inside locks (adaptive spinning before parking).
  • Cons: it burns a whole CPU core while waiting, can starve other threads, and hurts power usage and co-located workloads.

Use it only on dedicated cores, for very short expected waits. Otherwise use park/blocking. The condition variable must be volatile (or atomic), or the loop may never see the update. Thread.onSpinWait() (Java 9) emits CPU pause hints.

Q13. What's the difference between LongAdder and AtomicLong?

Short answer:

  • AtomicLong: a single variable updated by CAS. Under heavy contention, many threads fail and retry CAS on the same cache line, and throughput collapses.
  • LongAdder (Java 8): striped counters. There's a base value plus an array of Cells (padded to avoid false sharing). Contended threads update different cells, and sum() adds them up. It gives much higher write throughput under contention, but:
    • sum() is not an atomic snapshot while updates continue;
    • there's no compareAndSet;
    • it uses more memory.

Use LongAdder for metrics and hit counters (write-heavy, read occasionally). Use AtomicLong for sequence numbers or IDs, where each caller needs the exact new value. LongAccumulator generalises this to any associative function (max, min).

Q14. What are the atomic classes, and what are their limitations?

Short answer: java.util.concurrent.atomic provides lock-free, thread-safe single variables:

  • AtomicInteger, AtomicLong, AtomicBoolean, AtomicReference;
  • the array variants (AtomicIntegerArray);
  • field updaters (AtomicLongFieldUpdater);
  • AtomicStampedReference/AtomicMarkableReference;
  • LongAdder/LongAccumulator/DoubleAdder.

Limitations:

  • One variable at a time. You can't atomically update two atomics together. Use a lock, or an immutable object in an AtomicReference.
  • Contention hotspots: CAS retry storms under heavy writes (use LongAdder).
  • ABA for references.
  • updateAndGet functions may run several times, so they must be side-effect free.
  • Compound business logic (check a balance, then debit, then log) still needs higher-level synchronisation, or database transactions.
  • Each wrapper object costs allocation and memory. VarHandles on plain fields avoid that.

Q15. What is Phaser?

Short answer: A reusable, multi-phase barrier with a dynamic number of parties (Java 7):

  • threads can register and deregister at any time (register, arriveAndDeregister);
  • the phaser advances phase by phase, when all registered parties arrive (arriveAndAwaitAdvance);
  • it supports non-waiting arrival (arrive), tiering (a tree of phasers, for huge numbers of parties), and a termination hook (onAdvance).

It generalises both CyclicBarrier (fixed parties) and CountDownLatch (one-shot). Use it when workers join or leave between phases: for example, a crawler that spawns tasks per level.

Q16. How does Exchanger work?

Short answer: A synchronisation point where two threads swap objects. Each calls exchange(myObject), which blocks until the partner arrives, then each receives the other's object. The classic use is a double-buffering pipeline: a producer fills buffer A while the consumer drains buffer B, and at the rendezvous they exchange buffers, with no allocation or copying. It's rarely used in business code. BlockingQueues are more common.

Q17. What is thread affinity?

Short answer: Pinning a thread to a specific CPU core (or set of cores), so the OS scheduler doesn't migrate it. That keeps its caches warm, and avoids interference, which gives more predictable latency. It's used in low-latency trading, and with busy-spinning threads, together with isolated cores (isolcpus), NUMA-aware memory placement and IRQ steering. Java has no standard API for it. Use OS tools (taskset, numactl, cgroups cpuset) or libraries (OpenHFT Java-Thread-Affinity, through JNA). In Kubernetes: the static CPU manager policy with guaranteed QoS pods.

Key points to cover:

  • A related concept is "affinity" in schedulers, like ForkJoin workers preferring their own deques: soft locality, rather than hard pinning.

Q18. How can you implement a thread-safe singleton without synchronisation?

Short answer: Two standard approaches, both relying on class initialisation guarantees (the JVM initialises a class exactly once, thread-safely, and lazily on first use):

public final class Registry {                                  // initialisation-on-demand holder (Bill Pugh)
    private Registry() { }
    private static final class Holder { static final Registry INSTANCE = new Registry(); }
    public static Registry getInstance() { return Holder.INSTANCE; }   // Holder loads on the first call
}

public enum IdGenerator {                                      // enum singleton: serialisation- and reflection-safe
    INSTANCE;
    private final AtomicLong next = new AtomicLong();
    public long nextId() { return next.incrementAndGet(); }
}

The JVM's class-init lock does the synchronisation once. After that, access is plain field reads. An eager static final field is also thread-safe (it's just not lazy).

Key points to cover:

  • Double-checked locking is the "manual" alternative. It does use synchronized (once), and requires a volatile field.
  • In Spring applications, container-managed singleton beans make hand-written singletons largely unnecessary.

Q19. How would you refactor non-thread-safe code to make it thread-safe using synchronisation?

Short answer:

  1. Identify the shared mutable state, and the invariants that span it: fields changed by several threads, and check-then-act sequences.
  2. First, try to eliminate the sharing: make objects immutable, confine them to a thread, move to local variables, or use concurrent collections or atomics.
  3. Where locking is needed:
    • choose one lock per invariant, and guard every access (reads too) with the same lock;
    • make compound actions atomic;
    • keep critical sections short, with no I/O or callbacks inside;
    • document it (@GuardedBy("lock")).
  4. Lock on a private final object, not this or a public object, so outside code can't interfere.
  5. Consider ordering and deadlocks if you need several locks.
  6. Test it: concurrent unit tests with many threads, jcstress or Lincheck, and static analysis (Error Prone, SpotBugs).
public class InventoryBefore {                 // races: check-then-act on the shared map
    private final Map<String, Integer> stock = new HashMap<>();
    public boolean reserve(String sku, int qty) {
        int available = stock.getOrDefault(sku, 0);
        if (available < qty) return false;
        stock.put(sku, available - qty);
        return true;
    }
}

public class InventoryAfter {
    private final Object lock = new Object();
    @GuardedBy("lock") private final Map<String, Integer> stock = new HashMap<>();
    public boolean reserve(String sku, int qty) {
        synchronized (lock) {                  // the whole check-then-act is atomic
            int available = stock.getOrDefault(sku, 0);
            if (available < qty) return false;
            stock.put(sku, available - qty);
            return true;
        }
    }
}
// Or, without an explicit lock: stock.computeIfPresent(sku, (k, v) -> v >= qty ? v - qty : v) on a ConcurrentHashMap

Follow-up questions this topic invites — and their answers

Q: What is a fair lock, and why isn't it the default? A: A fair lock (new ReentrantLock(true)) grants access in FIFO arrival order, which prevents starvation. It's much slower under contention: no barging, and more context switches. Non-fair locks let a thread that's already running grab a free lock immediately, which gives better throughput.

Q: What is a Condition, and why is it better than wait/notify? A: lock.newCondition() gives separate wait sets per condition (for example notFull/notEmpty in a bounded buffer). You can signal exactly the right waiters, with timed and interruptible awaits, all tied to an explicit Lock.

Q: What is the ABA problem, concretely? A: Thread 1 reads the head A of a lock-free stack, and is delayed. Thread 2 pops A and B, then pushes A back. Thread 1's CAS(A → A.next) succeeds, but A.next is now stale (B was removed), which corrupts the stack. Versioned references, or never reusing nodes (GC-safe allocation), prevent it.

Q: How would you rate-limit calls with a Semaphore, compared with a token bucket? A: A Semaphore limits concurrency (in-flight calls), not rate (calls per second). For rate, use a token bucket (Guava's RateLimiter, Bucket4j, or Resilience4j's RateLimiter) that refills permits over time.

Previous

Threads, Executors & ForkJoin Internals — Interview Questions

Next

Java Memory Model, volatile, Fences & ThreadLocal — Interview Questions

AI Tutor

Lesson: Locks, Atomics, CAS & Synchronizers — Interview Questions

Quick actions

AI responses can be inaccurate. Verify critical information.