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.
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.
Architect's Guide
Dedicated to sharing programmer-architect skills—Java backend, system, microservice, and distributed architectures—to help you become a senior architect.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.