在Java多线程开发中,多个线程往往需要协作完成同一个任务,这就需要线程之间进行通信,同时要保证通信过程的安全性,避免出现数据竞争、脏读等问题。Java提供了多种成熟的机制来实现线程间的安全通信,下面逐一介绍。

一、使用volatile关键字实现轻量级通信
volatile是Java提供的最轻量级的线程间通信方式,它可以保证变量的可见性,即一个线程修改了volatile修饰的变量,其他线程能立即感知到这个修改。
volatile适合用来传递简单的状态标记,比如控制线程的启动和停止。下面是一个示例:
public class VolatileCommunicationDemo {
// 用volatile修饰状态变量,保证可见性
private static volatile boolean isStop = false;
public static void main(String[] args) throws InterruptedException {
// 工作线程,循环检查isStop状态
Thread workerThread = new Thread(() -> {
System.out.println("工作线程启动,开始执行任务");
while (!isStop) {
// 模拟任务执行
}
System.out.println("工作线程收到停止信号,结束运行");
});
workerThread.start();
// 主线程休眠1秒,让工作线程先运行
Thread.sleep(1000);
// 主线程修改isStop状态,工作线程会立即感知
isStop = true;
System.out.println("主线程发送停止信号");
}
}
需要注意的是,volatile只能保证可见性,不能保证原子性,所以如果通信的内容是需要复合操作的数据,不适合单独使用volatile。
二、synchronized配合wait和notify机制
这是Java最传统的线程通信方式,基于对象的内置锁(监视器锁)实现,适合需要线程等待和唤醒的场景,比如生产者消费者模型。
核心方法是Object类的三个方法:wait()、notify()、notifyAll(),这三个方法必须在synchronized修饰的代码块或方法中调用,否则会抛出IllegalMonitorStateException。
wait():当前线程释放锁,进入等待状态,直到被其他线程唤醒或者超时notify():唤醒同一个锁对象上等待的一个线程,具体唤醒哪个线程由JVM决定notifyAll():唤醒同一个锁对象上等待的所有线程
下面是一个简单的生产者消费者示例:
import java.util.LinkedList;
import java.util.Queue;
public class WaitNotifyDemo {
// 共享队列,容量为5
private static final Queue<Integer> queue = new LinkedList<>();
private static final int MAX_SIZE = 5;
// 共享锁对象
private static final Object lock = new Object();
// 生产者线程
static class Producer extends Thread {
@Override
public void run() {
int num = 0;
while (true) {
synchronized (lock) {
// 队列满了就等待
while (queue.size() == MAX_SIZE) {
try {
System.out.println("队列已满,生产者等待");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 生产数据
queue.add(num);
System.out.println("生产数据:" + num);
num++;
// 唤醒消费者
lock.notifyAll();
}
// 模拟生产耗时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
// 消费者线程
static class Consumer extends Thread {
@Override
public void run() {
while (true) {
synchronized (lock) {
// 队列空了就等待
while (queue.isEmpty()) {
try {
System.out.println("队列为空,消费者等待");
lock.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
// 消费数据
int val = queue.poll();
System.out.println("消费数据:" + val);
// 唤醒生产者
lock.notifyAll();
}
// 模拟消费耗时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) {
Producer producer = new Producer();
Consumer consumer = new Consumer();
producer.start();
consumer.start();
}
}
三、Lock配合Condition接口实现精准通信
JDK 1.5之后引入了Lock接口和Condition接口,相比传统的wait和notify机制,Condition可以实现更精准的线程唤醒,比如可以区分唤醒生产者还是消费者,不需要唤醒所有等待线程。
Condition的await()方法对应wait(),signal()对应notify(),signalAll()对应notifyAll(),同样需要在获取锁之后调用。
下面是使用Condition改造的生产者消费者示例:
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo {
private static final Queue<Integer> queue = new LinkedList<>();
private static final int MAX_SIZE = 5;
private static final Lock lock = new ReentrantLock();
// 生产者等待条件
private static final Condition producerCondition = lock.newCondition();
// 消费者等待条件
private static final Condition consumerCondition = lock.newCondition();
static class Producer extends Thread {
@Override
public void run() {
int num = 0;
while (true) {
lock.lock();
try {
while (queue.size() == MAX_SIZE) {
System.out.println("队列已满,生产者等待");
producerCondition.await();
}
queue.add(num);
System.out.println("生产数据:" + num);
num++;
// 精准唤醒消费者
consumerCondition.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
static class Consumer extends Thread {
@Override
public void run() {
while (true) {
lock.lock();
try {
while (queue.isEmpty()) {
System.out.println("队列为空,消费者等待");
consumerCondition.await();
}
int val = queue.poll();
System.out.println("消费数据:" + val);
// 精准唤醒生产者
producerCondition.signal();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public static void main(String[] args) {
new Producer().start();
new Consumer().start();
}
}
四、使用并发工具类实现线程通信
Java并发包java.util.concurrent中提供了很多工具类,也可以用来实现线程间的安全通信,常见的有以下几类:
1. CountDownLatch
适合一个线程等待多个线程完成任务的场景,比如主线程等待所有子线程执行完再继续。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
int taskId = i;
new Thread(() -> {
System.out.println("任务" + taskId + "开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("任务" + taskId + "执行完成");
// 计数减1
latch.countDown();
}).start();
}
// 主线程等待所有任务完成
latch.await();
System.out.println("所有子任务完成,主线程继续执行");
}
}
2. CyclicBarrier
适合一组线程互相等待,直到所有线程都到达某个屏障点再继续执行的场景。
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> {
System.out.println("所有线程都到达屏障,开始下一步操作");
});
for (int i = 0; i < threadCount; i++) {
int threadId = i;
new Thread(() -> {
System.out.println("线程" + threadId + "执行第一阶段任务");
try {
Thread.sleep(1000);
System.out.println("线程" + threadId + "到达屏障");
barrier.await();
} catch (Exception e) {
Thread.currentThread().interrupt();
}
System.out.println("线程" + threadId + "执行第二阶段任务");
}).start();
}
}
}
五、不同方式的适用场景对比
为了帮助开发者选择合适的通信方式,下面整理了不同方式的适用场景对比:
| 通信方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| volatile | 传递简单状态标记 | 轻量级,性能高 | 不保证原子性,不适合复杂数据通信 |
| synchronized+wait/notify | 传统线程协作,生产者消费者等场景 | 无需额外引入依赖,使用简单 | 唤醒不精准,容易唤醒无关线程 |
| Lock+Condition | 需要精准唤醒特定类型线程的场景 | 唤醒精准,灵活性高 | 需要手动释放锁,使用复杂度稍高 |
| 并发工具类 | 特定协作场景(如等待多个任务完成、线程互相等待) | 封装性好,无需手动处理锁和等待逻辑 | 仅适用于特定场景,通用性稍弱 |
六、注意事项
在使用线程间通信时,需要注意以下几点:
- 调用
wait()、await()方法时,一定要用while循环检查条件,而不是if,因为线程被唤醒后条件可能已经不满足了,避免虚假唤醒问题 - 使用Lock时,一定要在
finally块中释放锁,避免锁泄漏 - 不要随意使用
notifyAll()或者signalAll(),如果可以精准唤醒,优先选择精准唤醒,减少不必要的线程上下文切换 - 共享数据的操作要保证原子性,必要时结合
Atomic类或者锁来保证
Java线程安全线程通信volatilewait_notify修改时间:2026-07-13 08:06:41