线程通信
线程通信:从 synchronized 到 Lock 的演进与实践
线程通信是多线程编程中的核心问题,用于协调多个线程间的执行顺序和资源共享。Java 提供了两种主要的线程通信机制:基于synchronized的wait/notify和基于Lock的Condition接口。本文将深入对比这两种机制,并解析其适用场景。
synchronized 与 Object 类的线程通信方法
核心方法
Java 的每个对象都继承自Object类,提供了三个关键方法用于线程间通信:
- wait():使当前线程释放对象锁并进入等待状态,直到被其他线程唤醒;
- notify():随机唤醒一个在该对象上等待的线程;
- notifyAll():唤醒所有在该对象上等待的线程。
使用规则
这些方法必须在synchronized修饰的代码块或方法中调用,否则会抛出IllegalMonitorStateException。
示例:交替打印 1-100
public class AlternatePrint {
private static final Object LOCK = new Object();
private static int count = 1;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
synchronized (LOCK) {
// 检查条件,避免虚假唤醒
while (count > 100) break;
System.out.println(Thread.currentThread().getName() + ": " + count++);
LOCK.notify(); // 唤醒其他线程
try {
if (count <= 100) LOCK.wait(); // 释放锁并等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "线程1");
Thread t2 = new Thread(() -> {
while (true) {
synchronized (LOCK) {
while (count > 100) break;
System.out.println(Thread.currentThread().getName() + ": " + count++);
LOCK.notify();
try {
if (count <= 100) LOCK.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}, "线程2");
t1.start();
t2.start();
}
}
虚假唤醒(Spurious Wakeup)
Java 规范允许wait()在没有被显式唤醒的情况下返回,称为 “虚假唤醒”。因此,正确的使用方式是将wait()放在while循环中,确保条件满足:
synchronized (obj) {
while (!condition) {
obj.wait();
}
// 执行操作
}
Lock 与 Condition 接口的线程通信
Condition 接口简介
JDK 1.5 引入了java.util.concurrent.locks包,提供了比synchronized更灵活的锁机制。Condition接口与Lock配合使用,替代了Object的wait/notify方法,提供更细粒度的线程控制:
- await():等价于
wait(); - signal():等价于
notify(); - signalAll():等价于
notifyAll()。
Condition 的优势
- 多个等待队列:一个
Lock可以创建多个Condition对象,每个对象管理独立的等待队列,实现更精准的线程唤醒; - 可中断等待:支持带超时的等待和可中断的等待;
- 公平锁支持:配合公平锁实现线程按顺序获取锁。
示例:基于 Condition 的交替打印
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class AlternatePrintWithCondition {
private static final Lock LOCK = new ReentrantLock();
private static final Condition CONDITION = LOCK.newCondition();
private static int count = 1;
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
LOCK.lock();
try {
while (count > 100) break;
System.out.println(Thread.currentThread().getName() + ": " + count++);
CONDITION.signal(); // 唤醒其他线程
if (count <= 100) CONDITION.await(); // 释放锁并等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
LOCK.unlock();
}
}
}, "线程1");
Thread t2 = new Thread(() -> {
while (true) {
LOCK.lock();
try {
while (count > 100) break;
System.out.println(Thread.currentThread().getName() + ": " + count++);
CONDITION.signal();
if (count <= 100) CONDITION.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
LOCK.unlock();
}
}
}, "线程2");
t1.start();
t2.start();
}
}
多条件唤醒示例:生产者 - 消费者模型
使用 synchronized 实现
public class ProducerConsumer {
private static final Object LOCK = new Object();
private static final Queue<Integer> QUEUE = new LinkedList<>();
private static final int MAX_SIZE = 5;
public static void main(String[] args) {
// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
synchronized (LOCK) {
// 队列满时等待
while (QUEUE.size() == MAX_SIZE) {
try {
LOCK.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
QUEUE.offer(i);
System.out.println("生产者生产: " + i);
LOCK.notifyAll(); // 唤醒消费者
}
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
while (true) {
synchronized (LOCK) {
// 队列空时等待
while (QUEUE.isEmpty()) {
try {
LOCK.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
Integer item = QUEUE.poll();
System.out.println("消费者消费: " + item);
if (item == 9) break; // 退出条件
LOCK.notifyAll(); // 唤醒生产者
}
}
});
producer.start();
consumer.start();
}
}
使用 Condition 实现(更清晰的等待 / 唤醒逻辑)
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumerWithCondition {
private static final Lock LOCK = new ReentrantLock();
private static final Condition NOT_FULL = LOCK.newCondition();
private static final Condition NOT_EMPTY = LOCK.newCondition();
private static final Queue<Integer> QUEUE = new LinkedList<>();
private static final int MAX_SIZE = 5;
public static void main(String[] args) {
// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
LOCK.lock();
try {
while (QUEUE.size() == MAX_SIZE) {
NOT_FULL.await(); // 队列满时等待
}
QUEUE.offer(i);
System.out.println("生产者生产: " + i);
NOT_EMPTY.signal(); // 通知消费者队列非空
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
LOCK.unlock();
}
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
while (true) {
LOCK.lock();
try {
while (QUEUE.isEmpty()) {
NOT_EMPTY.await(); // 队列空时等待
}
Integer item = QUEUE.poll();
System.out.println("消费者消费: " + item);
if (item == 9) break; // 退出条件
NOT_FULL.signal(); // 通知生产者队列非满
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
LOCK.unlock();
}
}
});
producer.start();
consumer.start();
}
}
synchronized 与 Condition 的对比
| 特性 | synchronized + wait/notify | Lock + Condition |
|---|---|---|
| 锁机制 | 内置锁,自动释放 | 显式锁,需手动释放(finally 块) |
| 等待队列数量 | 单一队列,无法区分唤醒类型 | 可创建多个 Condition,精准唤醒 |
| 公平性 | 非公平锁 | 支持公平锁 |
| 中断响应 | 不支持中断等待 | 支持可中断等待 |
| 超时机制 | 仅支持带超时的 wait | 支持灵活的超时设置 |
| 锁状态查询 | 无法查询 | 可查询锁状态 |
| 代码复杂度 | 较低,语法简洁 | 较高,需手动管理锁 |
最佳实践建议
1. 选择原则
- 优先使用 synchronized:简单场景、锁竞争不激烈时,语法简洁,JVM 对其有优化;
- 使用 Lock+Condition:需要灵活的锁控制(如多条件唤醒、可中断锁、公平锁)。
2. 注意事项
- 避免死锁:确保线程按相同顺序获取锁,并在 finally 块中释放锁;
- 防范虚假唤醒:始终使用
while循环检查条件; - 性能考量:高并发场景下,
ReentrantLock可能提供更好的性能,但需根据实际情况测试。