1. 多线程协作的核心困境
当两个线程需要相互配合完成某项任务时,比如生产者线程生产数据、消费者线程处理数据,就涉及到线程间的通信与协调。Java提供了wait()和notify()这套机制来实现线程间的等待/唤醒操作,但这两个方法的使用有个硬性规定:必须在同步代码块(synchronized block)中调用。这个看似简单的约束背后,隐藏着多线程编程中最精妙的设计考量。
我曾在分布式消息队列的开发中,因为忽视这个规则导致过消息重复消费的事故。当时在非同步块中调用wait(),虽然测试环境偶现问题,但线上流量激增时直接导致消息处理线程集体挂起。这个惨痛教训让我彻底理解了同步块的必要性——它不仅是语法要求,更是线程安全的生命线。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 同步块的三重防护机制
2.1 竞态条件的预防
假设没有同步块约束,考虑以下伪代码场景:
java复制// 线程A检查条件
if(!condition) {
// 此处可能发生线程切换
obj.wait();
}
// 线程B修改条件后通知
condition = true;
obj.notify();
当线程A检查condition为false后,如果在调用wait()前发生线程切换,线程B此时修改condition并调用notify(),随后线程A才执行wait(),这将导致线程A永久等待。同步块通过原子性地执行"检查-等待"操作,消除了这个竞态条件窗口。
2.2 内存可见性的保证
在x86架构下测试发现,非同步环境下的共享变量修改,其他线程可能需要毫秒级时间才能可见。而synchronized会建立happens-before关系,确保:
- 进入同步块时清空工作内存,强制从主内存读取最新值
- 退出同步块时立即刷新工作内存到主内存
- wait()调用会释放锁,但会建立特殊的内存屏障
我曾用JMH做过测试:非同步环境下变量可见性延迟可达5ms以上,而同步块内始终保证纳秒级可见。
2.3 线程状态管理的安全性
wait()会改变线程状态为WAITING,并加入对象的等待集;notify()要从等待集中选取线程唤醒。这些操作必须:
- 原子性地判断线程状态
- 防止并发修改等待集
- 确保唤醒操作与条件判断的原子性
在HotSpo
