synchronized 还是很重吗?
2026/7/4 4:06:59 网站建设 项目流程

一句话

synchronized早就不是"重量级锁"了。JDK6 引入了锁升级机制(偏向锁 → 轻量级锁 → 重量级锁),大部分情况下只到轻量级锁(自旋),性能和ReentrantLock差不多。​95% 场景用 synchronized 就够了​。


synchronized 锁升级过程

无锁状态 ↓ 第一个线程来竞争 偏向锁(只记录线程 ID,CAS 一次搞定) ↓ 第二个线程来竞争 轻量级锁(自旋等待,不阻塞) ↓ 自旋超过一定次数 重量级锁(OS 互斥锁,线程阻塞/唤醒)

偏向锁

适用场景​:只有一个线程反复加同一把锁(比如StringBuffer只被一个线程操作)。

原理: JVM 在对象头里记录当前持有锁的线程 ID 下次同一个线程再来加锁 → 检查 ID 一致 → 直接通过(零开销) 相当于"VIP 通道",因为大部分锁实际上只有一个线程在用

JDK15 开始默认关闭偏向锁(-XX:-UseBiasedLocking),因为现代并发场景下偏向锁维护成本 > 收益。面试提到这个加分。

轻量级锁

适用场景​:两个线程交替使用同一把锁,竞争不激烈。

原理: 在线程栈帧里创建 Lock Record,CAS 把对象头指向 Lock Record 另一个线程发现锁被占了 → 不阻塞,而是自旋(空转等着) 像打电话占线 → 不挂断,每隔几秒重拨一次 自旋一定次数后还拿不到 → 升级为重量级锁

重量级锁

适用场景​:多线程激烈竞争。

原理: 依赖操作系统层面的互斥锁(mutex) 线程拿不到锁 → 进入阻塞状态 → 涉及用户态↔内核态切换 这个切换非常昂贵(微秒级)

为什么说 synchronized "不重"了?

锁升级的核心思路:用最轻的方式解决问题 只有一个线程 → 偏向锁(几乎零开销) 两个线程交替 → 轻量级锁(自旋,比阻塞轻得多) 真正激烈竞争 → 重量级锁(最后手段) 大部分实际场景停留在轻量级锁阶段,根本到不了重量级锁。 所以" synchronized 太重"这个说法在 JDK6 之后就不成立了。

synchronized vs ReentrantLock

维度synchronizedReentrantLock
本质JVM 内置关键字java.util.concurrent 包的类(基于 AQS)
锁释放自动(出作用域释放)必须手动unlock()(finally 中)
可中断不行,等锁时只能傻等lockInterruptibly()可响应中断
公平锁只支持非公平支持公平 / 非公平
条件变量只有一组wait()/notify()多个Conditionawait/signal
尝试加锁不支持tryLock(timeout)超时放弃
锁升级有(偏向 → 轻量 → 重量)无此机制
性能JDK6+ 差距很小高竞争下略优

ReentrantLock 底层:AQS

ReentrantLock 内部继承 AQS(AbstractQueuedSynchronizer) AQS 核心设计: state 变量:int,0=未锁定,>0=锁定次数(ReentrantLock 可重入) CLH 双向队列:排队的线程链表 加锁流程: ① CAS 尝试把 state 从 0 改为 1 ② 成功 → 拿到锁 ③ 失败 → 当前线程封装成 Node 入 CLH 队列 → 阻塞等待 ④ 前一个线程释放锁 → unpark 队列中下一个线程 → 唤醒 公平 vs 非公平: 非公平锁(默认):新线程先 CAS 抢,抢不到再排队 → 吞吐量更高 公平锁:严格按 CLH 队列顺序来 → 不饥饿但吞吐量低

面试追问:“AQS 除了 ReentrantLock 还被谁用了?” →Semaphore(信号量)、CountDownLatchReentrantReadWriteLock都基于 AQS。


标准写法对比

synchronized

// 简洁,自动释放,不会忘publicsynchronizedvoidtransfer(Accounttarget,intamount){this.balance-=amount;target.balance+=amount;}

ReentrantLock

// 必须手动释放,忘记就死锁privatefinalReentrantLocklock=newReentrantLock();publicvoidtransfer(Accounttarget,intamount){lock.lock();try{this.balance-=amount;target.balance+=amount;}finally{lock.unlock();// 必须在 finally 里}}

🎙 面试回答模板

"synchronized 和 ReentrantLock 的区别,我从五个维度来说: 第一,锁释放方式不同: synchronized 是 JVM 关键字,出作用域自动释放; ReentrantLock 是 API 层面的,必须手动在 finally 中 unlock。 第二,可中断性不同: synchronized 等锁时不能中断,只能一直等; ReentrantLock 可以用 lockInterruptibly 响应中断。 第三,条件变量不同: synchronized 只有一组 wait/notify; ReentrantLock 支持多个 Condition,可以做更精细的线程通信。 第四,锁的公平性: synchronized 只支持非公平锁; ReentrantLock 可以选择公平锁或非公平锁。 第五,尝试加锁: synchronized 不支持,ReentrantLock 可以 tryLock 设置超时。 不过有一点要纠正——synchronized 不是重量级锁。 JDK6 之后有锁升级机制:偏向锁 → 轻量级锁 → 重量级锁, 大部分情况只到轻量级锁,性能差距很小。 所以 95% 场景用 synchronized 就够了, 需要灵活控制中断、超时、多条件时才用 ReentrantLock。"

参考来源

  • 《Java 并发编程的艺术》第 2 章
  • JDK8java.util.concurrent.locks.ReentrantLock源码
  • JDK8AbstractQueuedSynchronizer源码

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询