Fundamentals 10 min read

Java Thread Communication Techniques: volatile, wait/notify, CountDownLatch, ReentrantLock with Condition, and LockSupport

This article demonstrates five Java thread‑communication approaches—using a volatile flag, Object.wait()/notify(), CountDownLatch, ReentrantLock with Condition, and LockSupport—each illustrated with complete code examples and explanations of their behavior and limitations.

Architect's Guide
Architect's Guide
Architect's Guide
Java Thread Communication Techniques: volatile, wait/notify, CountDownLatch, ReentrantLock with Condition, and LockSupport

The article presents a scenario where thread A adds the string "abc" to a list ten times and, after the fifth addition, thread B should be notified to perform its business logic. It then explores five different Java mechanisms to achieve this inter‑thread communication.

1. Using the volatile keyword – This method relies on shared memory; both threads monitor a volatile boolean flag that becomes true when the list reaches size five. The code shows thread B looping until the flag is set, while thread A updates the flag after the fifth insertion.

public class TestSync {
    static volatile boolean notice = false;
    public static void main(String[] args) {
        List
list = new ArrayList<>();
        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 threadB = new Thread(() -> {
            while (true) {
                if (notice) { System.out.println("线程B收到通知,开始执行自己的业务..."); break; }
            }
        });
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        threadA.start();
    }
}

2. Using Object.wait() / Object.notify() – This classic approach requires a lock object and the synchronized block. Thread A calls notify() when the list size reaches five; thread B waits on the same lock until it is notified.

public class TestSync {
    public static void main(String[] args) {
        Object lock = new Object();
        List
list = new ArrayList<>();
        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();
                }
            }
        });
        Thread threadB = new Thread(() -> {
            while (true) {
                synchronized (lock) {
                    if (list.size() != 5) {
                        try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
                    }
                    System.out.println("线程B收到通知,开始执行自己的业务...");
                }
            }
        });
        threadB.start();
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        threadA.start();
    }
}

3. Using JUC CountDownLatch – Introduced in Java 5, this utility simplifies coordination. Thread A calls countDown() at the fifth insertion; thread B blocks on await() until the latch reaches zero.

public class TestSync {
    public static void main(String[] args) {
        CountDownLatch latch = new CountDownLatch(1);
        List
list = new ArrayList<>();
        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) latch.countDown();
            }
        });
        Thread threadB = new Thread(() -> {
            while (true) {
                if (list.size() != 5) {
                    try { latch.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 a Condition – This method mirrors wait/notify but uses explicit lock objects. Thread A signals the condition when the list reaches five; thread B awaits the condition before proceeding.

public class TestSync {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        List
list = new ArrayList<>();
        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 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 – This low‑level utility provides park/unpark methods that do not require a lock. Thread B parks until thread A unparks it after the fifth insertion.

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();
    }
}

The article concludes that while each method works, some (volatile, wait/notify, ReentrantLock) keep the notifying thread from releasing the lock immediately, whereas utilities like CountDownLatch and LockSupport offer clearer semantics. A final note advertises a WeChat public account for further Java resources.

JavaconcurrencyvolatileReentrantLockThread CommunicationLockSupportCountDownLatchwait-notify
Architect's Guide
Written by

Architect's Guide

Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.

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.