线程通信:从 synchronized 到 Lock 的演进与实践
线程通信是多线程编程中的核心问题,用于协调多个线程间的执行顺序和资源共享。Java 提供了两种主要的线程通信机制:基于synchronized
的wait/notify
和基于Lock
的Condition
接口。本文将深入对比这两种机制,并解析其适用场景。
synchronized 与 Object 类的线程通信方法
核心方法
Java 的每个对象都继承自Object
类,提供了三个关键方法用于线程间通信:
- wait():使当前线程释放对象锁并进入等待状态,直到被其他线程唤醒;
- notify():随机唤醒一个在该对象上等待的线程;
- notifyAll():唤醒所有在该对象上等待的线程。
使用规则
这些方法必须在synchronized
修饰的代码块或方法中调用,否则会抛出IllegalMonitorStateException
。
示例:交替打印 1-100
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| 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
循环中,确保条件满足:
1 2 3 4 5 6
| 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 的交替打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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 实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| 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 实现(更清晰的等待 / 唤醒逻辑)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| 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
可能提供更好的性能,但需根据实际情况测试。
v1.3.10