news 2026/4/16 15:58:33

【synchronized 与 ReentrantLock + Condition 的深度对比 Plus版】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【synchronized 与 ReentrantLock + Condition 的深度对比 Plus版】

Java并发编程:synchronized 与 ReentrantLock + Condition 的深度对比——从Monitor队列到惊群效应与精确唤醒

  • 前言
  • 正文
    • 一、每个Java对象天生都能当锁?Monitor的底层结构
      • 形象比喻:锁对象就像一个会议室。
      • 关键区别:
    • 二、synchronized的线程状态流转全过程
      • 完整过程演绎
        • 1. 抢锁阶段:
        • 2. 条件不满足,主动等待:
        • 3. 被通知唤醒:
      • 为什么被唤醒后还要再抢锁?
      • 惊群效应的根源:
    • 三、ReentrantLock + Condition的高明之处:按等待性质分类的多个队列
    • 四、生产者-消费者完整代码对比
    • 五、为什么JDK的高并发容器都用Condition?
  • 总结:从synchronized到Condition的进化

前言

最近和很多读者深入交流了Java多线程中两个核心的等待/通知机制:synchronized + wait/notify 和 ReentrantLock + Condition。很多同学在学习时都会疑惑:为什么Condition更高级?为什么高并发容器都用Condition?为什么synchronized容易出现“惊群效应”?

今天我们把这些问题全部串起来,用最通俗易懂的语言,从底层实现讲起,一步步把Monitor的两个队列、线程状态流转、惊群效应、Condition的精确唤醒全部讲清楚。读完这篇,你会对Java并发等待/通知机制有彻底的理解!

正文

一、每个Java对象天生都能当锁?Monitor的底层结构

先来一个基础但超级重要的知识点:Java中每个对象都可以作为锁对象,因为它们都继承了Object类的wait()、notify()、notifyAll()方法。
当一个对象第一次被用作锁(比如进入synchronized(obj)块)时,JVM会给它分配一个叫Monitor(对象监视器)的结构。Monitor就像这个对象的“管家”,里面主要有三样东西:

  • Owner:当前持有锁的线程(锁的“主人”)。只有Owner才能执行临界区代码。
  • Entry List(入口等待队列):还没抢到锁的线程被park(阻塞)在这里,等着锁释放后一起竞争。
  • Wait Set(等待队列):已经抢到锁,但业务条件不满足,主动调用wait()的线程,会被放到这里深度睡眠。

形象比喻:锁对象就像一个会议室。

  • Owner:正在会议室里开会的人。
  • Entry List:门口排队的想进来开会的人(抢不到门票)。
  • Wait Set:已经进过会议室,但发现“现在没内容可讨论”,主动出去休息室睡觉的人。

关键区别:

  1. Entry List里的线程:是因为抢不到锁而阻塞。
  2. Wait Set里的线程:是抢到了锁,但条件不满足,主动释放锁去睡觉。

二、synchronized的线程状态流转全过程

我们用生产者-消费者场景来一步步看线程是怎么流动的。

完整过程演绎

1. 抢锁阶段:

线程A先进入synchronized(obj),成功抢到锁 → 成为Owner,进入临界区。

其他线程(B、C、D)也想进来,发现锁被占了 → 被park到Entry List,在门口排队。

2. 条件不满足,主动等待:

线程A在临界区发现“缓冲区满了,没地方放东西”。

调用obj.wait():

立即释放锁(Owner变为null,会议室开门)。

自己走进Wait Set(休息室)睡觉,完全不参与抢锁。

锁释放后,Entry List的线程(B、C、D)被唤醒,开始竞争锁。

3. 被通知唤醒:

假设线程B抢到锁,消费后调用obj.notify()或notifyAll()。

notify():从Wait Set随机选一个线程唤醒。

notifyAll():把Wait Set里所有线程都唤醒。

超级重要!被唤醒的线程不会立刻拿到锁!

它们会从Wait Set转移到Entry List。

然后和Entry List里其他线程一起重新竞争锁。

只有抢到锁后,才能继续执行wait()后面的代码。

为什么被唤醒后还要再抢锁?

  • wait()必须在持有锁的情况下调用,从wait()恢复也必须重新持有锁,确保条件检查和修改是原子的。
    这也是为什么要用while检查条件(防止虚假唤醒),而不是if。

惊群效应的根源:

  1. synchronized只有一个Wait Set,所有等待原因的线程(生产者因为“满”、消费者因为“空”)都挤在这里。
  2. 一次notifyAll() → 把所有人转移到Entry List → 所有人抢锁 → 大部分醒来发现条件不满足,又wait()回去 → 大范围惊群,大量无谓的上下文切换,性能很差。

三、ReentrantLock + Condition的高明之处:按等待性质分类的多个队列

  1. Java为了解决synchronized的痛点,引入了java.util.concurrent.locks包。ReentrantLock基于AQS(AbstractQueuedSynchronizer),底层更灵活。

  2. AQS有一个同步队列(类似Entry List,用来排队等锁)。
    lock.newCondition()可以创建多个Condition对象,每个Condition都有自己独立的等待队列(类似Wait Set,但可以有多个)。

JavaLock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 生产者专用:缓冲区满时等待
Condition notEmpty = lock.newCondition(); // 消费者专用:缓冲区空时等待

关键点:不是每个线程一个队列,而是按“等待性质”分类

一个Condition队列里可以有多个线程,但这些线程的等待原因是相同的(同一种性质)。
比如:notFull队列里可能有50个生产者在等(都因为缓冲区满)。
notEmpty队列里可能有30个消费者在等(都因为缓冲区空)。

await/signal的工作原理:

await():释放锁 → 进入这个Condition的专属队列睡觉。

signal():从这个队列头部取一个线程 → 转移到AQS同步队列 → 去抢锁(和synchronized唤醒后去Entry List一样)。

signal()默认只唤醒一个(先进先出),signalAll()唤醒整个队列(但只限同类线程)。

惊群范围大幅缩小:

  1. 消费者消费后,只调用notEmpty.signal() → 只唤醒notEmpty队列里的消费者。
    生产者的50个线程完全不受影响,继续睡觉。
  2. 即使signalAll(),也只影响同类线程,远比synchronized的“唤醒所有人”好太多。
    加上signal()默认只醒一个,在大多数场景几乎杜绝了惊群。

“遥控器”比喻:Condition对象就像一个遥控器,你用哪个遥控器,就操作哪个队列,完全隔离。

四、生产者-消费者完整代码对比

synchronized版本(容易大范围惊群)

JavapublicclassBuffer{privatefinal Object lock=newObject();privatefinal Queue<Integer>queue=newLinkedList<>();privatefinal int capacity=10;publicvoidproduce(int item)throws InterruptedException{synchronized(lock){while(queue.size()==capacity){lock.wait();// 所有生产者进入同一个Wait Set}queue.offer(item);lock.notifyAll();// 唤醒所有人:生产者+消费者 → 大惊群}}publicintconsume()throws InterruptedException{synchronized(lock){while(queue.isEmpty()){lock.wait();// 所有消费者也进同一个Wait Set}int item=queue.poll();lock.notifyAll();// 同上returnitem;}}}

ReentrantLock + Condition版本(按性质分类,精确唤醒)

JavapublicclassBuffer{privatefinal Lock lock=newReentrantLock();privatefinal Condition notFull=lock.newCondition();// 生产者队列privatefinal Condition notEmpty=lock.newCondition();// 消费者队列privatefinal Queue<Integer>queue=newLinkedList<>();privatefinal int capacity=10;publicvoidproduce(int item)throws InterruptedException{lock.lock();try{while(queue.size()==capacity){notFull.await();// 只进生产者专属队列}queue.offer(item);notEmpty.signal();// 只唤醒一个消费者(推荐)}finally{lock.unlock();}}publicintconsume()throws InterruptedException{lock.lock();try{while(queue.isEmpty()){notEmpty.await();// 只进消费者专属队列}int item=queue.poll();notFull.signal();// 只唤醒一个生产者}finally{lock.unlock();}}}

新版本代码更清晰、性能更高。

五、为什么JDK的高并发容器都用Condition?

打开JDK源码:

ArrayBlockingQueue、LinkedBlockingQueue、ConcurrentHashMap(部分)等底层几乎全是用ReentrantLock + Condition。

原因:

多个独立队列 + 精确唤醒
支持超时等待(awaitNanos)、可中断、公平锁等高级特性
在高并发下性能远超synchronized

总结:从synchronized到Condition的进化

  1. synchronized + wait/notify:简单易用,但只有一个Wait Set,所有线程混在一起。wait()释放锁进Wait Set,notify后转移到Entry List再抢锁,notifyAll()容易造成大范围惊群。
  2. ReentrantLock + Condition:按等待性质分类多个队列,一个队列里多个同类线程。await/signal操作独立,signal()默认只醒一个,惊群范围大幅缩小,几乎杜绝。

推荐:简单场景用synchronized,复杂等待条件(如生产者-消费者、读写锁)强烈建议用Lock + Condition。

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c5721d2340f843d7bddc55fcf6369142.jpeg#pic_center

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 14:32:03

OSG-OpenSceneGraph安装配置VS2022(全网最简单)

网上几乎所有教程都需要先下载源码进行编译&#xff0c;过程复杂繁琐&#xff0c;而且容易踩坑报错。今天出一期懒人版配置OSG方法&#xff0c;放心食用。一、下载博主已经编译好的OSG库通过百度网盘分享的文件&#xff1a;vs2022 6... 链接&#xff1a;https://pan.baidu.com/…

作者头像 李华
网站建设 2026/4/16 12:57:00

基于单片机的智能购物车设计

一、设计背景与核心需求 传统超市购物车仅具备承载功能&#xff0c;存在商品统计繁琐、找货耗时、排队结账拥堵等问题&#xff0c;影响购物效率与体验。基于单片机的智能购物车&#xff0c;融合自动识别、路径导航、结算支付等功能&#xff0c;可实现商品自动计价、智能导购、快…

作者头像 李华
网站建设 2026/4/16 12:47:00

基于单片机的防酒驾酒精检测报警装置设计

第一章&#xff1a;系统设计目标与需求分析 本装置旨在通过实时酒精浓度检测与联动控制&#xff0c;从源头预防酒驾行为&#xff0c;适用于私家车、出租车等场景。核心需求包括&#xff1a;高精度酒精检测&#xff0c;范围0-200mg/100mL&#xff08;覆盖酒驾、醉驾标准&#xf…

作者头像 李华
网站建设 2026/4/16 14:27:06

Flutter-OH SMS Autofill 插件完整使用教程

Flutter-OH SMS Autofill 插件完整使用教程 一、插件介绍 一直以来&#xff0c;大家在Flutter开发鸿蒙应用的过程中&#xff0c;其中一个热点就是三方库的使用&#xff0c; 今天我们来看一下如何在鸿蒙平台上使用sms_autofill sms_autofill 是一个强大的 Flutter 插件&…

作者头像 李华