Multithreading and Concurrency are among the most advanced and commonly asked topics in Java interviews. Understanding inter-thread communication, synchronization tools, and concurrency utilities helps you write efficient and thread-safe applications.

This article covers Top Advanced Multithreading Questions in Java (Part 2) — explained with code, real-world analogies, and interview tips.


1️⃣ What is inter-thread communication?

Inter-thread communication allows multiple threads to cooperate with each other efficiently.

For example, one thread might produce data, and another consumes it. Instead of busy-waiting, threads use methods like wait(), notify(), and notifyAll() to communicate.

Analogy: Think of a restaurant — the chef (producer) waits until a waiter (consumer) serves the dish. The waiter then notifies the chef to cook the next order.


2️⃣ What are wait(), notify(), and notifyAll()?

  • wait() — Makes the current thread wait until another thread calls notify() or notifyAll() on the same object.
  • notify() — Wakes up one thread that is waiting on the object's monitor.
  • notifyAll() — Wakes up all threads waiting on that object.

class SharedResource {
    synchronized void produce() throws InterruptedException {
        System.out.println("Producing...");
        wait(); // releases lock and waits
        System.out.println("Resumed after notify");
    }

    synchronized void consume() {
        System.out.println("Consuming...");
        notify(); // wakes up waiting thread
    }
}

3️⃣ Can we call wait() outside a synchronized block?

No. The wait(), notify(), and notifyAll() methods must be called from within a synchronized block or method.

Otherwise, the JVM throws an IllegalMonitorStateException because the current thread doesn’t own the object’s monitor.


4️⃣ What happens if a thread calls wait() without owning the lock?

The JVM throws IllegalMonitorStateException because only a thread that owns an object's monitor (via synchronization) can call wait() or notify() on it.


5️⃣ Why wait(), notify(), and notifyAll() are in Object class, not Thread class?

Because these methods act on the object’s monitor lock, not on the thread itself. Every object in Java has a monitor, so placing them in the Object class allows all objects to be used for synchronization.


6️⃣ What is a deadlock?

A deadlock occurs when two or more threads are waiting for each other to release resources, and none can continue.


class DeadlockExample {
    final Object lock1 = new Object();
    final Object lock2 = new Object();

    void method1() {
        synchronized(lock1) {
            System.out.println("Thread1 locked lock1");
            synchronized(lock2) {
                System.out.println("Thread1 locked lock2");
            }
        }
    }

    void method2() {
        synchronized(lock2) {
            System.out.println("Thread2 locked lock2");
            synchronized(lock1) {
                System.out.println("Thread2 locked lock1");
            }
        }
    }
}

Here, Thread1 locks lock1 and waits for lock2, while Thread2 does the opposite — resulting in a deadlock.


7️⃣ How can you detect and prevent deadlocks?

Detection:

  • Use thread dumps (e.g., jstack) to identify deadlocked threads.
  • Use monitoring tools like VisualVM or JConsole.

Prevention:

  • Always acquire locks in a consistent order.
  • Avoid nested synchronization.
  • Use tryLock() with timeout to avoid indefinite waiting.
  • Prefer high-level concurrency utilities (e.g., Semaphore, ReentrantLock).

8️⃣ What is livelock?

Livelock occurs when threads keep responding to each other’s actions and never progress — though they are not blocked.

Example: Two people trying to pass through a narrow hallway keep stepping aside simultaneously — they never collide, but they also never move forward.


9️⃣ What is starvation in threads?

Starvation happens when a thread never gets CPU time because other high-priority threads keep running.

Prevention:

  • Use fair scheduling (like ReentrantLock(true)).
  • Avoid setting extreme priorities unnecessarily.

🔟 What is a monitor in Java?

A monitor is an internal mechanism in every Java object that allows only one thread at a time to execute synchronized code.

When a thread enters a synchronized block, it acquires the object’s monitor; when it exits, it releases the monitor.


11️⃣ Explain reentrant synchronization.

Reentrant synchronization allows the same thread to acquire the same lock multiple times without getting blocked.


synchronized void outer() {
    inner(); // same thread can call this
}

synchronized void inner() {
    System.out.println("Reentrant lock allows this");
}

12️⃣ Difference between synchronized and ReentrantLock

AspectsynchronizedReentrantLock
TypeKeywordClass in java.util.concurrent.locks
Lock HandlingAutomaticManual (lock()/unlock())
Try LockNot possibletryLock() available
FairnessNo fairnessCan be fair (new ReentrantLock(true))

13️⃣ Advantages of using ReentrantLock over synchronized

  • Supports timeout using tryLock()
  • Can check if a lock is held
  • Allows fair scheduling
  • Provides multiple condition variables

14️⃣ How do you use tryLock() in Java?


ReentrantLock lock = new ReentrantLock();
if (lock.tryLock()) {
    try {
        System.out.println("Got the lock!");
    } finally {
        lock.unlock();
    }
} else {
    System.out.println("Couldn't acquire lock!");
}

15️⃣ What is ReadWriteLock in Java?

ReadWriteLock allows multiple threads to read a resource simultaneously, but only one thread to write.


ReadWriteLock rwLock = new ReentrantReadWriteLock();
rwLock.readLock().lock();
System.out.println("Reading...");
rwLock.readLock().unlock();

Useful when reads are frequent but writes are rare.


16️⃣ What is a Semaphore?

Semaphore is a concurrency utility that controls access to a shared resource using permits.


Semaphore sem = new Semaphore(2); // only 2 threads allowed
sem.acquire();
System.out.println("Thread acquired permit");
sem.release();

It’s commonly used in resource pools and connection management.


17️⃣ What is CountDownLatch used for?

CountDownLatch lets one or more threads wait until a set of tasks complete.


CountDownLatch latch = new CountDownLatch(3);

for (int i = 1; i <= 3; i++) {
    new Thread(() -> {
        System.out.println("Task done by " + Thread.currentThread().getName());
        latch.countDown();
    }).start();
}

latch.await(); // waits until count reaches 0
System.out.println("All tasks completed!");

18️⃣ Explain CyclicBarrier with an example.

CyclicBarrier lets a group of threads wait for each other to reach a common point (barrier) before continuing.


CyclicBarrier barrier = new CyclicBarrier(3, 
    () -> System.out.println("All threads reached the barrier"));

for (int i = 1; i <= 3; i++) {
    new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " waiting");
        try { barrier.await(); } catch (Exception e) {}
        System.out.println(Thread.currentThread().getName() + " continues");
    }).start();
}

19️⃣ Difference between CyclicBarrier and CountDownLatch

FeatureCountDownLatchCyclicBarrier
ResettableNoYes
PurposeOne thread waits for othersAll threads wait for each other
Action on completionNo actionOptional Runnable executed

20️⃣ What is Exchanger in Java concurrency?

Exchanger allows two threads to swap objects safely.


Exchanger exchanger = new Exchanger<>();

new Thread(() -> {
    try {
        String msg = exchanger.exchange("Hello from Thread A");
        System.out.println("Thread A got: " + msg);
    } catch (Exception e) {}
}).start();

new Thread(() -> {
    try {
        String msg = exchanger.exchange("Hi from Thread B");
        System.out.println("Thread B got: " + msg);
    } catch (Exception e) {}
}).start();

21️⃣ What is Phaser in Java concurrency?

Phaser is a flexible synchronization barrier, an advanced alternative to CyclicBarrier and CountDownLatch.


Phaser phaser = new Phaser(3);

for (int i = 1; i <= 3; i++) {
    new Thread(() -> {
        System.out.println("Phase 1 by " + Thread.currentThread().getName());
        phaser.arriveAndAwaitAdvance();
        System.out.println("Phase 2 by " + Thread.currentThread().getName());
    }).start();
}

Threads wait for each other at each phase and can reuse the barrier for multiple phases.


22️⃣ What is volatile keyword used for?

volatile ensures that the value of a variable is always read from the main memory — not from a thread’s local cache.


volatile boolean flag = true;

while (flag) {
    // thread keeps running until flag becomes false
}

Useful for visibility between threads but doesn’t provide atomicity.


23️⃣ Difference between volatile and synchronized

Aspectvolatilesynchronized
PurposeEnsures visibilityEnsures visibility and atomicity
LockNo lock usedLock-based
PerformanceFasterSlower

24️⃣ Why is AtomicInteger faster than synchronized methods?

AtomicInteger uses low-level CPU instructions (CAS — Compare And Swap) for atomic operations, avoiding the overhead of locking.


AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // atomic, lock-free

25️⃣ What are atomic classes in Java concurrency?

Atomic classes in the java.util.concurrent.atomic package provide lock-free, thread-safe operations on single variables.

Common Classes:

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean
  • AtomicReference

They are highly efficient and should be used for counters, flags, and shared variables where full synchronization isn’t needed.


✅ Conclusion

Understanding Java concurrency utilities like Lock, Semaphore, CountDownLatch, and Phaser helps you write robust, deadlock-free, and efficient multithreaded programs.

For interviews, explain the difference between lock-based and lock-free approaches — and when to use each.


Next Read:
👉 Top 25 Basic Multithreading Interview Questions with detailed answers
👉 Top advance multithreading interview questions and answer