5 Practical Ways to Implement Thread Communication in Java
This article compares five Java thread‑communication techniques—volatile flag, Object.wait()/notify(), CountDownLatch, ReentrantLock with Condition, and LockSupport—by presenting concrete code examples, explaining their underlying mechanisms, and discussing their advantages and drawbacks.
Two threads, A and B, are used to illustrate inter‑thread communication: thread A adds the string "abc" to a list ten times, and when the list size reaches five, thread B should be notified to execute its business logic. The article explores five implementation approaches, each accompanied by a complete code sample and a step‑by‑step analysis of how the notification is delivered.
1. Using a volatile flag (shared‑memory model)
The simplest method relies on a shared volatile boolean notice variable. Thread A sets notice = true when the list size becomes five; thread B continuously polls the flag and proceeds once it sees true. The code demonstrates the need to start thread B first, then pause briefly before starting thread A so that B can observe the change.
public class TestSync {
static volatile boolean notice = false;
public static void main(String[] args) {
List<String> 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();
}
}While extremely simple, this approach incurs busy‑waiting and does not guarantee timely notification on all JVMs.
2. Using Object.wait() / Object.notify()
Here a dedicated lock object is introduced. Thread A enters a synchronized(lock) block, adds elements, and calls lock.notify() when the size reaches five. Thread B repeatedly acquires the same lock, checks the list size, and calls lock.wait() if the condition is not met.
public class TestSync {
public static void main(String[] args) {
Object lock = new Object();
List<String> 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收到通知,开始执行自己的业务...");
break;
}
}
});
threadB.start();
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
threadA.start();
}
}The output shows that notify() wakes the waiting thread but does not release the lock; only after A exits the synchronized block can B acquire the lock and continue. This demonstrates the classic “notify‑then‑release‑lock” behavior.
3. Using JUC CountDownLatch
Since JDK 1.5, the java.util.concurrent package provides high‑level synchronization utilities. A CountDownLatch initialized with a count of 1 replaces the shared flag. Thread A calls countDownLatch.countDown() when the list size reaches five; thread B calls await() and blocks until the latch reaches zero.
public class TestSync {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
List<String> 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) countDownLatch.countDown();
}
});
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();
}
}This approach eliminates manual lock handling and makes the intent clearer, but it still requires a shared state (the list) to be examined by B.
4. Using ReentrantLock with Condition
Replacing intrinsic locks with an explicit ReentrantLock and a Condition yields similar semantics to wait()/notify(). Thread A locks, adds elements, and calls condition.signal() at size 5. Thread B locks, checks the size, and calls condition.await() if the condition is not satisfied.
public class TestSync {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<String> 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();
}
}Like the intrinsic‑monitor version, the signaling thread still holds the lock after signal(), so B cannot proceed until A releases it, making the code more verbose without clear benefit.
5. Using LockSupport
LockSupportprovides low‑level park/unpark primitives that decouple the order of waiting and waking. Thread B calls LockSupport.park() when the list size is not five; thread A calls LockSupport.unpark(threadB) exactly when the size reaches five. No explicit lock is required.
public class TestSync {
public static void main(String[] args) {
List<String> 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();
}
}This method is flexible because the waking thread does not need to hold any lock, but it requires a reference to the target thread, which may be inconvenient in some designs.
Comparison and Recommendations
volatile flag : simplest syntax, but suffers from busy‑waiting and no ordering guarantees beyond visibility.
wait/notify : classic monitor‑based solution; demonstrates that notify() does not release the lock, which can cause subtle ordering issues.
CountDownLatch : high‑level JUC utility; eliminates explicit lock handling and clearly expresses a one‑time hand‑off.
ReentrantLock + Condition : mirrors wait/notify with explicit lock objects; adds verbosity without solving the lock‑release problem.
LockSupport : low‑level park/unpark; avoids holding locks during wake‑up, but requires thread references.
For a one‑time hand‑off like the example, CountDownLatch offers the cleanest code. If repeated signaling is needed, a Condition or LockSupport may be appropriate, while the volatile flag should be reserved for very simple scenarios where busy‑waiting is acceptable.
Source: blog.csdn.net/ChineseSoftware/article/details/118390388 (author: JFS_Study). Content is reproduced for educational purposes only.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
