// Java Interview — Senior Level

🔬 PART 7: Advanced Java Concurrency — Expert Level (Q351–380)

StampedLock, Scoped Values, Disruptor, NUMA, reactive streams, JFR profiling, expert-level patterns.

30 Questions
2 Mid
11 Advanced
17 Expert
351 . StampedLock (Java 8+) EXPERT

StampedLock is a higher-performance alternative to ReadWriteLock with three modes:

StampedLock lock = new StampedLock();

// Optimistic read — no lock acquired, just get a stamp
long stamp = lock.tryOptimisticRead();
int x = this.x, y = this.y;
if (!lock.validate(stamp)) { // check if write happened during read
    stamp = lock.readLock(); // fall back to real read lock
    try { x = this.x; y = this.y; }
    finally { lock.unlockRead(stamp); }
}

// Write lock
long writeStamp = lock.writeLock();
try { this.x = newX; this.y = newY; }
finally { lock.unlock(writeStamp); }

Optimistic reads are perfect for read-dominant workloads: no lock acquisition, just validate that no write occurred. Falls back to pessimistic if invalidated.

> StampedLock is NOT reentrant (unlike ReentrantReadWriteLock). Calling a stamped lock method from a thread that already holds it causes deadlock.

352 . Scoped Values (Java 21) EXPERT

ScopedValue is the recommended replacement for ThreadLocal in the virtual thread era:

static final ScopedValue<User> CURRENT_USER = ScopedValue.newInstance();

// Bind a value for the scope of a computation
ScopedValue.where(CURRENT_USER, authenticatedUser)
           .run(() -> processRequest()); // value visible in entire call subtree

// Access anywhere in the call tree
User user = CURRENT_USER.get();

Why better than ThreadLocal for virtual threads:

  • Immutable — value can't be changed during scope (thread-safe by design)
  • Automatically cleaned up when scope ends (no remove() needed)
  • Works correctly with structured concurrency (child tasks inherit parent's values)
  • No risk of memory leaks in thread pools
353 . Virtual Threads Deep Dive (Java 21) EXPERT

Virtual threads are lightweight JVM-managed threads (not OS threads). You can create millions with minimal overhead.

// Create virtual thread
Thread.ofVirtual().start(() -> blockingIO());

// Virtual thread executor — one virtual thread per task
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (int i = 0; i < 1_000_000; i++)
        executor.submit(this::handleRequest);
}

Internals:

  • Mapped M:N onto platform (OS carrier) threads
  • When a virtual thread blocks (I/O, sleep, lock), it unmounts from carrier thread; carrier serves other virtual threads
  • Stack is heap-allocated, starts ~kilobytes vs ~1MB for OS threads
  • No changes to Thread API — drop-in compatible

Pinning problem — virtual thread is pinned (can't unmount) when:

  • Inside a synchronized block that does I/O
  • Calling native (JNI) code
// BEFORE: synchronized (causes pinning in virtual thread era)
synchronized (this) { jdbcConnection.executeQuery(); } // pins!

// AFTER: use ReentrantLock instead
ReentrantLock lock = new ReentrantLock();
lock.lock();
try { jdbcConnection.executeQuery(); } // virtual thread can unmount
finally { lock.unlock(); }
354 . Project Loom Impact on Architecture EXPERT

Project Loom (finalized Java 21) fundamentally changes Java concurrency with virtual threads and structured concurrency.

Impact on existing code:

  • Blocking JDBC, HTTP clients, file I/O become scalable
  • Thread-per-request model (Tomcat, Spring MVC) now handles millions of concurrent requests
  • No need to rewrite to reactive (WebFlux) for scalability

Framework support:

  • Spring Boot 3.2+: spring.threads.virtual.enabled=true
  • Tomcat/Jetty: virtual thread executor support
  • JDBC: most drivers compatible (except if synchronized internally)

When to still use reactive:

  • Built-in backpressure operators
  • Fine-grained streaming pipelines
  • Existing reactive codebase (refactoring cost not worth it)
355 . Memory Visibility with final Fields EXPERT

The Java Memory Model gives final fields special guarantees: their values are guaranteed visible to all threads without synchronization after the object's constructor completes (as long as this doesn't escape the constructor).

class ImmutablePoint {
    final int x;
    final int y;

    ImmutablePoint(int x, int y) {
        this.x = x;
        this.y = y;
        // 'this' must not escape here (don't register this in constructor!)
    }
}

// Safe publication — another thread reading x and y after the constructor
// sees the correct values without synchronization

Unsafe publication — sharing object reference before constructor completes:

// DON'T DO THIS
ImmutablePoint point;
ImmutablePoint(int x, int y) {
    ExternalRegistry.register(this); // 'this' escapes before fields are set!
    this.x = x;
    this.y = y;
}
356 . Java Flight Recorder (JFR) for Concurrency Profiling EXPERT

JFR (built into JVM since Java 11) is invaluable for diagnosing concurrency issues in production:

# Start recording
jcmd <pid> JFR.start duration=60s filename=app.jfr

# Or programmatically
Recording recording = new Recording();
recording.enable("jdk.ThreadStart");
recording.enable("jdk.MonitorEnter");
recording.enable("jdk.MonitorWait");
recording.enable("jdk.ThreadSleep");
recording.start();

Key JFR events for concurrency:

  • jdk.MonitorEnter — lock acquisition attempts (detect contention)
  • jdk.MonitorWait — wait() calls with durations
  • jdk.ThreadBlocked — threads blocked on locks
  • jdk.ThreadSleep — sleep events
  • jdk.VirtualThreadPinned — virtual thread pinning events (Java 21+)

Analyze with JDK Mission Control (JMC): Flame graphs, lock contention, thread activity timelines.

357 . Disruptor Pattern (LMAX Disruptor) EXPERT

The LMAX Disruptor is a high-performance inter-thread messaging library that achieves extremely low latency by using:

Traditional Queue:    Disruptor:
Producer → Queue → Consumer    Producer → RingBuffer → Consumer
         (locks, GC)                    (lock-free, pre-allocated)

Key concepts:

  • RingBuffer: Fixed-size circular array, pre-allocated; no GC pressure
  • Sequence: Atomic counter tracking progress for each producer/consumer
  • WaitStrategy: How consumers wait for events (BusySpin, Yielding, Blocking, Sleeping)
  • EventProcessor: Threads consuming events from the ring buffer

Why faster than BlockingQueue:

  • No locks (CAS only)
  • Cache-friendly (array, not linked nodes)
  • No garbage (pre-allocated event objects reused)
  • False sharing avoided with padding

Used by: Log4j2's async loggers, financial trading systems, game engines.

358 . Thread Affinity and NUMA Awareness EXPERT

For ultra-low-latency systems, thread-to-CPU core affinity eliminates OS scheduling overhead:

// Using Java Thread Affinity library (OpenHFT)
try (AffinityLock lock = AffinityLock.acquireCore()) {
    // This thread is now pinned to a specific core
    performLatencySensitiveWork();
}

NUMA (Non-Uniform Memory Access): In multi-socket servers, memory access cost depends on which socket the memory is on. Threads should access memory local to their CPU socket.

# JVM NUMA awareness
-XX:+UseNUMA
-XX:+UseParallelGC  # GC that's NUMA-aware

ForkJoinPool NUMA:

  • Java 14+: ForkJoinPool has NUMA-aware allocation option
  • Critical for big-data processing on multi-socket machines
359 . Non-Blocking I/O (NIO) and Concurrency ADVANCED

Java NIO's Selector enables one thread to manage many I/O channels:

Selector selector = Selector.open();
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.register(selector, SelectionKey.OP_ACCEPT);

while (true) {
    selector.select(); // blocks until at least one channel ready
    Set<SelectionKey> selectedKeys = selector.selectedKeys();
    Iterator<SelectionKey> iter = selectedKeys.iterator();
    while (iter.hasNext()) {
        SelectionKey key = iter.next();
        if (key.isAcceptable()) { handleAccept(key); }
        if (key.isReadable()) { handleRead(key); }
        iter.remove();
    }
}

NIO.2 Async I/O (Java 7+): Callback-based, no selector needed:

AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);
channel.read(buffer, 0, null, new CompletionHandler<Integer, Void>() {
    public void completed(Integer bytesRead, Void attachment) { process(buffer); }
    public void failed(Throwable exc, Void attachment) { handleError(exc); }
});

With virtual threads, blocking I/O is now equivalent in scalability to NIO for most use cases.

360 . Concurrent Data Structures — ConcurrentSkipListMap EXPERT

ConcurrentSkipListMap is a lock-free, sorted, thread-safe map:

ConcurrentSkipListMap<Integer, String> map = new ConcurrentSkipListMap<>();
map.put(3, "three");
map.put(1, "one");
map.put(2, "two");

// Sorted access operations — all thread-safe without locking
map.firstKey();          // 1
map.lastKey();           // 3
map.headMap(2);          // {1=one}
map.tailMap(2);          // {2=two, 3=three}
map.floorKey(2);         // 2 (greatest key ≤ 2)
map.ceilingKey(2);       // 2 (smallest key ≥ 2)

Skip list internal: Probabilistic data structure with multiple layers. Each level skips over more elements. Expected O(log n) for all operations, fully lock-free using CAS.

When to use over ConcurrentHashMap: When you need sorted key access + range queries + concurrent writes.

361 . Reactive Streams Specification EXPERT

The Reactive Streams specification (Java 9 java.util.concurrent.Flow) defines a standard for asynchronous stream processing with non-blocking backpressure:

// Publisher produces items
Publisher<T> publisher = ...;

// Subscriber consumes items
publisher.subscribe(new Subscriber<T>() {
    Subscription subscription;

    public void onSubscribe(Subscription s) {
        this.subscription = s;
        s.request(10); // request 10 items (backpressure signal)
    }
    public void onNext(T item) {
        process(item);
        subscription.request(1); // request one more
    }
    public void onError(Throwable t) { handleError(t); }
    public void onComplete() { cleanup(); }
});

Project Reactor (Spring WebFlux) and RxJava implement this spec. Key types:

  • Mono<T>: 0 or 1 item asynchronously
  • Flux<T>: 0 to N items asynchronously
362 . Executor Shutdown and Graceful Termination MID

Proper executor shutdown is critical for production applications:

ExecutorService executor = Executors.newFixedThreadPool(4);

// Submit tasks...

// Graceful shutdown pattern
executor.shutdown(); // stop accepting new tasks
try {
    if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
        executor.shutdownNow(); // cancel in-progress tasks
        if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
            log.error("Executor did not terminate");
        }
    }
} catch (InterruptedException e) {
    executor.shutdownNow();
    Thread.currentThread().interrupt(); // preserve interrupt status
}

Spring Boot graceful shutdown:

server:
  shutdown: graceful
spring:
  lifecycle:
    timeout-per-shutdown-phase: 30s

Virtual thread executor with try-with-resources (Java 19+):

try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // executor.close() called automatically — waits for all tasks to complete
    IntStream.range(0, 1000).forEach(i -> executor.submit(() -> handleRequest(i)));
}
363 . Thread Interruption Protocol ADVANCED

Proper thread interruption is important for clean cancellation:

// WRONG: Swallowing interrupt
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    // DON'T just catch and ignore!
}

// RIGHT: Restore interrupt status or propagate
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt(); // restore interrupt status
    return; // or throw new RuntimeException(e) to propagate
}

// Check interrupt in long-running loops
while (!Thread.currentThread().isInterrupted()) {
    doWork();
}

interrupt() vs stop(): stop() is deprecated (unsafe — leaves objects in inconsistent state). interrupt() is the cooperative cancellation mechanism — the thread must check and respond to the interrupt.

364 . CompletableFuture Exception Handling Patterns ADVANCED
// handle() — runs on success OR failure, transforms result
CompletableFuture<String> result = fetchUser()
    .handle((user, ex) -> {
        if (ex != null) return "default-user"; // on error
        return user.getName();                  // on success
    });

// whenComplete() — runs on success OR failure, doesn't transform
fetchUser().whenComplete((user, ex) -> {
    if (ex != null) metrics.incrementErrorCount();
    else metrics.incrementSuccessCount();
    // returns same CompletableFuture with original value/exception
});

// exceptionally() — only on failure, provide fallback
fetchUser().exceptionally(ex -> User.anonymous());

// Timeout (Java 9+)
fetchUser()
    .orTimeout(5, TimeUnit.SECONDS)          // complete exceptionally on timeout
    .completeOnTimeout(User.anonymous(), 5, TimeUnit.SECONDS); // provide default
365 . ThreadPoolExecutor Rejection Policies EXPERT

When the thread pool queue is full and all threads are busy, the RejectedExecutionHandler decides what to do:

PolicyBehaviorUse Case
AbortPolicy (default)Throws RejectedExecutionExceptionFail fast; caller handles
CallerRunsPolicyCaller thread executes the taskNatural backpressure; slows producer
DiscardPolicySilently discards the taskFire-and-forget, acceptable loss
DiscardOldestPolicyRemoves oldest queued task, retriesFresh tasks are priority
// Custom rejection policy — log + fallback
new ThreadPoolExecutor(4, 8, 60L, SECONDS,
    new ArrayBlockingQueue<>(200),
    (task, executor) -> {
        metrics.incrementRejectedTasks();
        log.warn("Task rejected, falling back to sync execution");
        if (!executor.isShutdown()) task.run(); // run in caller's thread
    }
);
366 . Fork-Join Best Practices ADVANCED
// GOOD: Use fork() for async subtask, compute() for current thread's work
protected Long compute() {
    if (size <= THRESHOLD) {
        return computeSequentially();
    }
    SumTask left = new SumTask(lo, mid);
    left.fork(); // dispatch left to pool asynchronously
    Long rightResult = new SumTask(mid, hi).compute(); // use current thread
    Long leftResult = left.join(); // wait for left
    return leftResult + rightResult;
}

// BAD: Forking both and joining both
left.fork(); right.fork();
return left.join() + right.join(); // current thread sits idle during both joins!

Tuning ForkJoinPool:

ForkJoinPool pool = new ForkJoinPool(
    Runtime.getRuntime().availableProcessors(),
    ForkJoinPool.defaultForkJoinWorkerThreadFactory,
    null,    // uncaught exception handler
    true     // asyncMode (FIFO vs LIFO for local queue)
);
367 . Concurrent Collections Performance Comparison EXPERT
Use CaseBest ChoiceWhy
Read-heavy map (no order needed)ConcurrentHashMapLock-free reads
Sorted concurrent mapConcurrentSkipListMapLock-free, sorted
High-contention counterLongAdderStriped cells reduce contention
Single-producer/consumer queueArrayBlockingQueueSimple, bounded
Multi-producer/consumer queueLinkedTransferQueueBetter throughput
Read-dominant listCopyOnWriteArrayListLock-free reads
Priority-based work queuePriorityBlockingQueueHeap-based, thread-safe
368 . Memory Leaks in Concurrent Code ADVANCED

Common sources:

  1. ThreadLocal without remove():
// In thread pools, ThreadLocal values survive thread reuse
static final ThreadLocal<HeavyObject> tl = new ThreadLocal<>();
// After request: tl.remove() MUST be called!
  1. Static ConcurrentHashMap growing unbounded:
static final Map<String, byte[]> cache = new ConcurrentHashMap<>();
// No eviction policy → heap exhaustion over time
// Fix: Use Caffeine/Guava Cache with size/time eviction
  1. Listener/Observer not unregistered:
eventBus.subscribe(this); // strong reference from eventBus to this
// If 'this' is expected to be garbage collected, it won't be
// Fix: Use WeakReference listeners or explicit unsubscribe
  1. Async tasks holding references:
CompletableFuture.supplyAsync(() -> process(largeObject)); // largeObject captured
// If the future is stored, largeObject stays alive until future completes
369 . Concurrency Patterns — Active Object Pattern ADVANCED

The Active Object pattern decouples method execution from invocation. Each active object has its own thread and a queue for method requests:

class ActiveLogger {
    private final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
    private final Thread worker;

    ActiveLogger() {
        worker = Thread.ofVirtual().start(() -> {
            while (!Thread.interrupted()) {
                try {
                    queue.take().run(); // process log requests sequentially
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
    }

    public void log(String message) {
        queue.offer(() -> writeToFile(message)); // non-blocking submit
    }
}

Used in: Async logging (Log4j2's async appender), actor frameworks (Akka), UI event loops.

370 . Java Memory Model — Safe Publication EXPERT

Safe publication means making an object reference available to other threads in a way that guarantees they see the object's fully initialized state.

Safe publication mechanisms:

  1. Initialize in static initializer (class loading guarantees visibility)
  2. Store in volatile field
  3. Store in final field (JMM final field guarantee)
  4. Store via lock: synchronized block or concurrent collection
// UNSAFE publication
class Holder {
    private int n;
    Holder(int n) { this.n = n; }
    void assertSanity() {
        if (n != n) throw new AssertionError(); // can actually happen!
    }
}
public Holder holder; // non-volatile, non-final — unsafe!

// SAFE publication
public volatile Holder holder;       // option 1
public final Holder holder = ...;   // option 2 (if in constructor)
// store in ConcurrentHashMap       // option 3
// store in synchronized block      // option 4
371 . Concurrent Programming with Records (Java 16+) ADVANCED

Records are inherently thread-safe due to immutability:

// Record components are final — naturally thread-safe
record Transaction(String id, BigDecimal amount, Instant timestamp) {}

// Safe to share across threads without synchronization
Transaction tx = new Transaction(uuid, amount, Instant.now());
// Multiple threads can read tx concurrently — no sync needed

Records in concurrent maps:

// Records make excellent ConcurrentHashMap keys (final hashCode/equals)
ConcurrentHashMap<TransactionKey, TransactionResult> results = new ConcurrentHashMap<>();

record TransactionKey(String accountId, String transactionId) {}
// hashCode and equals generated from final components — always consistent
372 . Virtual Threads and Thread-Local Best Practices EXPERT

With virtual threads, ThreadLocal use requires extra care:

// PROBLEM: ThreadLocal in virtual threads
// Each virtual thread gets its own ThreadLocal copy — potentially millions of copies!
static final ThreadLocal<HeavyConnectionPool> pool = new ThreadLocal<>();
// With 1M virtual threads → 1M ConnectionPool instances → OOM

// SOLUTION 1: Use ScopedValue instead (Java 21+)
static final ScopedValue<Connection> CONNECTION = ScopedValue.newInstance();
ScopedValue.where(CONNECTION, getConnection()).run(() -> doWork());

// SOLUTION 2: Pass dependencies explicitly (dependency injection)
void process(Connection conn, Data data) { ... } // explicit is clear

// SOLUTION 3: If ThreadLocal is needed, keep values lightweight
static final ThreadLocal<SimpleDateFormat> formatter = // OK — lightweight
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
373 . Concurrency in Microservices — Distributed Locking EXPERT

In distributed systems, local Java locks don't work across JVM instances. Need distributed coordination:

// Redis-based distributed lock (Redisson)
RLock lock = redissonClient.getLock("order-processing-lock-" + orderId);
try {
    boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS); // wait 10s, hold 30s
    if (!locked) throw new LockNotAcquiredException("Another instance processing order");
    processOrder(orderId);
} finally {
    if (lock.isHeldByCurrentThread()) lock.unlock();
}

// Database-based lock (Optimistic Locking with JPA @Version)
@Entity
public class Order {
    @Version
    private Long version; // automatically incremented, conflicts detected
}

Distributed lock challenges:

  • Clock drift between nodes can cause lock expiry issues
  • Network partitions can cause split-brain (two nodes think they hold the lock)
  • Redlock algorithm (multiple Redis masters) reduces risk but has controversies
374 . Concurrency Anti-Patterns ADVANCED

1. Double-Checked Locking without volatile (pre-Java 5 bug, still seen):

// BROKEN (without volatile):
private static Singleton instance;
if (instance == null) {
    synchronized (Singleton.class) {
        if (instance == null)
            instance = new Singleton(); // object partially visible without volatile!
    }
}
// FIX: Add volatile to instance field

2. Synchronized on non-final field:

// BROKEN: lock object can change
private Object lock = new Object();
synchronized(lock) { ... }
// If another thread assigns: lock = new Object(); — different lock!
// FIX: Make lock field final

3. Calling wait() without holding the lock:

// BROKEN: IllegalMonitorStateException
lock.wait(); // must be inside synchronized(lock)

4. Using HashMap in multi-threaded code:

// In Java 7: causes infinite loop (resize causes circular linked list)
// In Java 8+: race conditions, wrong results
// ALWAYS use ConcurrentHashMap for shared maps

5. sleep() as synchronization mechanism:

// BROKEN: Timing-based, fragile
Thread.sleep(1000); // hope the other thread is done by now
375 . Performance Monitoring for Concurrent Apps ADVANCED

Key metrics to monitor:

// Thread pool monitoring via JMX
ThreadPoolExecutor tpe = (ThreadPoolExecutor) executorService;
int active = tpe.getActiveCount();       // actively executing threads
long completed = tpe.getCompletedTaskCount();
int queueSize = tpe.getQueue().size();   // backlog
int poolSize = tpe.getPoolSize();

// Expose via Micrometer (Spring Boot Actuator)
new ExecutorServiceMetrics(executorService, "myPool", tags)
    .bindTo(meterRegistry);
// Exposes: pool.size, active.threads, queued.tasks, completed.tasks

JVM thread metrics:

# Via jstat
jstat -gcutil <pid> 1000  # GC stats every second

# Via JFR analysis
- Thread blocked time
- Lock contention heat map
- Thread CPU time breakdown

Alerts to set:

  • Queue size > 80% of capacity → producer-consumer imbalance
  • Active threads = pool size → all threads busy, consider scaling
  • Rejected task rate > 0 → pool overloaded
  • Average task duration trending up → downstream degradation
376 . Async Exception Handling Across Threads MID

Exceptions in background threads that aren't handled silently die:

// PROBLEM: Exception in thread dies silently
ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
    throw new RuntimeException("This silently disappears!");
});

// SOLUTION 1: Always get() the Future and handle exceptions
Future<?> future = executor.submit(() -> riskyWork());
try {
    future.get(); // throws ExecutionException wrapping original
} catch (ExecutionException e) {
    Throwable cause = e.getCause(); // original exception
    handleError(cause);
}

// SOLUTION 2: Set UncaughtExceptionHandler
ThreadFactory factory = r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler((thread, ex) -> {
        log.error("Uncaught exception in thread {}", thread.getName(), ex);
        metrics.incrementUnhandledErrors();
    });
    return t;
};

// SOLUTION 3: CompletableFuture with exceptionally
CompletableFuture.runAsync(() -> riskyWork())
    .exceptionally(ex -> { handleError(ex); return null; });
377 . Concurrent Collections — ConcurrentLinkedQueue vs LinkedBlockingQueue ADVANCED
ConcurrentLinkedQueueLinkedBlockingQueue
BlockingNo (returns null/false)Yes (take/put block)
BoundedNo (unbounded)Optional (can be bounded)
AlgorithmLock-free (CAS)Two-lock (head lock + tail lock)
Best forNon-blocking producers/consumersBlocking producer-consumer pattern
Sizesize() is O(n) (traverse)size() is O(1)
// Non-blocking usage with ConcurrentLinkedQueue
ConcurrentLinkedQueue<Task> queue = new ConcurrentLinkedQueue<>();
Task task = queue.poll(); // returns null if empty (non-blocking)
if (task != null) process(task);

// Blocking usage with LinkedBlockingQueue
LinkedBlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
Task task = queue.take(); // blocks until element available
378 . Intrinsic Lock Optimization in JVM EXPERT

Modern JVMs heavily optimize synchronized via several techniques:

1. Biased Locking (Java 1.6–14, removed in Java 21):

  • First thread to acquire a lock "biases" the object toward itself
  • Subsequent acquisitions by the same thread are nearly free (no CAS)
  • Only pays cost when another thread contends

2. Lightweight Locking (thin lock):

  • Low-contention case: object's mark word stores a CAS-based lock
  • No OS mutex, no thread blocking — just spinning

3. Heavyweight Locking (inflated lock):

  • High-contention: inflates to OS mutex with thread blocking
  • Standard monitor semantics

4. Lock Elision:

  • JIT detects that a lock is not accessible to other threads
  • Removes the lock entirely (e.g., synchronized on a local variable)

5. Lock Coarsening:

  • JIT merges adjacent synchronized blocks on the same monitor into one larger block
  • Reduces lock/unlock overhead in tight loops
379 . Working with CompletableFuture in Spring ADVANCED
@Service
public class DashboardService {

    @Async("asyncExecutor") // runs on asyncExecutor thread pool
    public CompletableFuture<UserProfile> getUserProfileAsync(Long userId) {
        return CompletableFuture.completedFuture(userService.getProfile(userId));
    }

    @Async("asyncExecutor")
    public CompletableFuture<List<Order>> getOrdersAsync(Long userId) {
        return CompletableFuture.completedFuture(orderService.getOrders(userId));
    }

    // Combine parallel calls
    public Dashboard getDashboard(Long userId) throws Exception {
        CompletableFuture<UserProfile> profileFuture = getUserProfileAsync(userId);
        CompletableFuture<List<Order>> ordersFuture = getOrdersAsync(userId);

        // Wait for both to complete
        CompletableFuture.allOf(profileFuture, ordersFuture).join();

        return new Dashboard(profileFuture.get(), ordersFuture.get());
    }
}

// Configure async executor in Spring
@Configuration
@EnableAsync
public class AsyncConfig {
    @Bean("asyncExecutor")
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);
        executor.setMaxPoolSize(50);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("async-");
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }
}
380 . Concurrency Interview Questions — Expert-Level Answers EXPERT

Q: How does ConcurrentHashMap achieve lock-free reads?

A: Each Node in the array has a volatile value field. Reads traverse nodes using volatile reads which have happens-before guarantees with the last volatile write. No lock needed because: (1) array reference is volatile, (2) node values are volatile, (3) structural modifications use CAS + synchronized on bin head which establish happens-before with readers.

Q: Why is volatile long needed for 64-bit values on 32-bit JVMs?

A: 64-bit long and double reads/writes are not guaranteed to be atomic on 32-bit JVMs (JMM allows tearing — two separate 32-bit operations). volatile upgrades these to atomic 64-bit operations.

Q: Can you deadlock with ReentrantLock and no nested locking?

A: Yes. If a thread acquires a ReentrantLock and the thread is interrupted/killed before calling unlock(), the lock is never released. Always use lock in try block with unlock() in finally.

Q: What is a memory barrier and when does Java insert one?

A: A memory barrier (fence) is a CPU instruction that prevents instruction reordering across it and forces cache flush. Java inserts barriers: before/after volatile read/write, on lock acquire/release, on Thread.start()/Thread.join(). This implements happens-before.

Q: Explain the difference between submit() and execute() in ExecutorService.

A: execute(Runnable) is fire-and-forget — no way to know when it completes or if it threw an exception. submit() returns a Future — you can call get() to block and retrieve results, and any exception thrown is wrapped in ExecutionException which you can access via future.get().