Backend Development 11 min read

Thread Communication in Java: volatile, wait/notify, CountDownLatch, ReentrantLock+Condition, and LockSupport

This article explains five Java thread‑communication techniques—volatile variables, Object.wait()/notify(), CountDownLatch, ReentrantLock with Condition, and LockSupport—providing code examples and detailed explanations of how each method works and its synchronization behavior, including sample programs that add elements to a list, demonstrate notification timing, and show how locks are acquired and released.

Top Architect
Top Architect
Top Architect
Thread Communication in Java: volatile, wait/notify, CountDownLatch, ReentrantLock+Condition, and LockSupport

In this tutorial a senior architect presents five common Java thread‑communication mechanisms.

Using the volatile keyword

Using Object.wait() / Object.notify()

Using JUC CountDownLatch

Using ReentrantLock with Condition

Using LockSupport

1. Using volatile

Based on the volatile keyword, multiple threads share a variable; when the variable changes, other threads can perceive the change and execute related business logic. This is the simplest shared‑memory approach.

public class TestSync {
    // Define a shared volatile flag
    static volatile boolean notice = false;

    public static void main(String[] args) {
        List
list = new ArrayList<>();
        // Thread A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                if (list.size() == 5) notice = true;
            }
        });
        // Thread B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                    break;
                }
            }
        });
        // Start B first
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        // Then start A
        threadA.start();
    }
}

2. Using Object.wait()/notify()

The Object class provides the fundamental thread‑communication methods wait() , notify() , and notifyAll() . They must be used inside a synchronized block; wait() releases the lock, while notify() does not release it immediately.

public class TestSync {
    public static void main(String[] args) {
        Object lock = new Object();
        List
list = new ArrayList<>();
        // Thread A
        Thread threadA = new Thread(() -> {
            synchronized (lock) {
                for (int i = 1; i <= 10; i++) {
                    list.add("abc");
                    System.out.println("线程A添加元素,此时list的size为:" + list.size());
                    try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                    if (list.size() == 5) lock.notify(); // wake up B
                }
            }
        });
        // Thread B
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
                break;
            }
        });
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        threadA.start();
    }
}

3. Using JUC CountDownLatch

Since JDK 1.5, the java.util.concurrent package offers utilities like CountDownLatch , which internally uses an AQS state to coordinate threads.

public class TestSync {
    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        List
list = new ArrayList<>();
        // Thread A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                if (list.size() == 5) countDownLatch.countDown();
            }
        });
        // Thread B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (list.size() != 5) {
                    try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); }
                }
                System.out.println("线程B收到通知,开始执行自己的业务...");
                break;
            }
        });
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        threadA.start();
    }
}

4. Using ReentrantLock with Condition

ReentrantLock combined with a Condition offers explicit lock control and separate wait‑sets, but the waking thread still must acquire the lock before proceeding.

public class TestSync {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        List
list = new ArrayList<>();
        // Thread A
        Thread threadA = new Thread(() -> {
            lock.lock();
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                if (list.size() == 5) condition.signal();
            }
            lock.unlock();
        });
        // Thread B
        Thread threadB = new Thread(() -> {
            lock.lock();
            if (list.size() != 5) {
                try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); }
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
            lock.unlock();
        });
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        threadA.start();
    }
}

5. Using LockSupport

LockSupport provides flexible park/unpark operations that do not depend on lock ownership, allowing a thread to block until another thread explicitly unparks it.

public class TestSync {
    public static void main(String[] args) {
        List
list = new ArrayList<>();
        final Thread threadB = new Thread(() -> {
            if (list.size() != 5) {
                LockSupport.park();
            }
            System.out.println("线程B收到通知,开始执行自己的业务...");
        });
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A添加元素,此时list的size为:" + list.size());
                try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                if (list.size() == 5) LockSupport.unpark(threadB);
            }
        });
        threadA.start();
        threadB.start();
    }
}

Each method has its own characteristics regarding lock release, notification timing, and code complexity, allowing developers to choose the most suitable approach for their concurrency scenarios.

JavaconcurrencyvolatileReentrantLockThread CommunicationLockSupportCountDownLatch
Top Architect
Written by

Top Architect

Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.