文章目录
Java中synchronized和ReentrantLock的区别主要体现在以下几个方面:
1、锁的实现方式: synchronized是Java内置的关键字,JVM层面实现;ReentrantLock是Java类库中的一个API。
2、锁的公平性: synchronized不保证公平性;而ReentrantLock可以通过构造函数设置公平性。
3、锁的灵活性: ReentrantLock提供更灵活的锁操作,它支持中断锁的获取、超时获取锁等高级功能。
4、条件变量支持: ReentrantLock可以与Condition类配合,实现分组唤醒等复杂的线程协作。
synchronized和ReentrantLock的区别详解
synchronized vs ReentrantLock:Java 锁界的 “躺平大爷” vs “内卷精英”:
如果说 Java 并发是 “线程过马路”,那synchronized和ReentrantLock就是俩 “交通管理员”—— 一个是按规矩办事、啥也不多管的退休大爷,一个是技能拉满、灵活应变的年轻精英。俩都能搞定 “线程不抢道”,但用法、能力、适用场景天差地别,今天就用 “人话 + 热梗” 扒得明明白白!
一、先定人设:俩锁的核心气质差异
| 锁类型 | 人设标签 | 核心气质 |
|---|---|---|
synchronized | 退休居委会大爷 | 体制内出身(JVM 内置),规则简单、自动干活,不用你操心,但认死理、功能单一 |
ReentrantLock | 互联网内卷精英 | 第三方创业(Java 代码实现),手动操作、技能点满,灵活可控,但需要自己操心细节 |
简单说:synchronized是 “全自动洗衣机”,按下开关就不管;ReentrantLock是 “手动挡跑车”,能漂移能加速,但得自己挂挡踩油门!
二、使用方式:一个 “躺平式”,一个 “内卷式”
2.1 synchronized:大爷帮你 “包办一切”
用synchronized就像过小区门禁,你只要走到门口(进入同步方法 / 代码块),大爷自动锁门;你走了(方法 / 代码块结束),大爷自动开门,全程 “零手动操作”,懒癌患者福音。
🚀 代码示例(3 种用法,全是自动档)
// 1. 同步方法:相当于“进大门,大爷直接拦着别人”
public synchronized void goHome() {
System.out.println("大爷守门:我进家了,其他人排队!");
}
// 2. 同步代码块:相当于“进客厅,大爷只拦客厅门”
public void watchTV() {
synchronized (this) { // this = 客厅门钥匙
System.out.println("大爷守门:我占客厅看电视,别人别来抢遥控器!");
}
}
// 3. 同步静态方法:相当于“进小区公共健身房,大爷拦健身房门”
public static synchronized void goGym() {
System.out.println("大爷守门:健身房被我占了,等我练完!");
}
核心特点: 语法极简,“自动加锁 + 自动释放”,哪怕代码抛异常,大爷也会乖乖开门(JVM 自动释放锁),绝不会出现 “锁没关” 的情况。
2.2 ReentrantLock:精英让你 “亲力亲为”
用ReentrantLock就像用智能门锁,你得自己刷卡(lock())进门,出门必须手动锁门(unlock()),还得把 “锁门” 这事刻进 DNA—— 忘了锁门,就等于 “敞着门睡觉”(死锁风险)。
✔️ 代码示例(手动挡,一步都不能错)
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
// 初始化智能门锁(可传true开启“公平模式”,后面讲)
private final ReentrantLock lock = new ReentrantLock();
public void goHome() {
lock.lock(); // 刷卡进门(手动加锁)
try {
System.out.println("精英管家:我进家了,其他人排队!");
} finally {
// 必须放finally!就算抛异常,也得锁门
lock.unlock(); // 手动锁门(释放锁)
}
}
// 进阶用法:尝试进门,5秒没进去就走(避免死等)
public void tryGoHome() throws InterruptedException {
// tryLock(5秒):最多等5秒,拿不到锁就返回false
if (lock.tryLock(5, java.util.concurrent.TimeUnit.SECONDS)) {
try {
System.out.println("精英管家:抢上门了!");
} finally {
lock.unlock();
}
} else {
System.out.println("精英管家:等5秒还没抢到,先去喝杯奶茶!");
}
}
}
核心特点:语法繁琐,但可控性拉满。能实现synchronized做不到的 “骚操作”(超时等待、可中断、公平锁),但必须手动释放锁,否则会导致 “锁泄漏”(其他线程永远拿不到锁)。
三、核心差异:一张表怼穿 “大爷” 和 “精英” 的差距

四、ReentrantLock 的 “骚操作”
4.1 公平锁:拒绝 “插队”,实现 “排队自由”
synchronized默认是 “非公平锁”—— 线程 A 刚释放锁,线程 C 就从旁边冲进来插队,比排队很久的线程 B 先拿到锁(大爷不管排队,谁猛谁上)
ReentrantLock可以通过构造函数开启 “公平锁”:
// 开启公平锁:线程必须按请求顺序拿锁,不允许加塞
private final ReentrantLock fairLock = new ReentrantLock(true);
适用场景:银行办理业务、秒杀活动排队等,需要 “先来后到” 的场景,避免线程饥饿(某些线程永远抢不到锁)。
4.2 可中断锁:等不及就 “溜”,不做 “死等冤种”
用synchronized时,线程 A 持有锁,线程 B 想拿锁只能死等 —— 就算等 1 小时,也不能中途放弃(除非线程被杀死),典型的 “冤种行为”。
ReentrantLock的lockInterruptibly()支持 “可中断”:
public void interruptibleLock() throws InterruptedException {
lock.lockInterruptibly(); // 等待时可以被中断
try {
// 临界区代码
} finally {
lock.unlock();
}
}
// 其他线程可以调用 thread.interrupt() 打断等待中的线程
适用场景:比如用户发起一个请求,超过 3 秒没响应就取消,这时可以中断等待锁的线程,避免资源浪费。
4.3 多个条件变量:分场景等待,不 “一刀切”
synchronized只有一个条件队列,用wait()唤醒时,会唤醒所有等待的线程(notifyAll()),哪怕有些线程等的条件还没满足(比如线程 A 等 “饭熟”,线程 B 等 “菜好”,唤醒后俩线程都得跑过来检查)。
ReentrantLock的Condition可以创建多个条件队列,精准唤醒:
import java.util.concurrent.locks.Condition;
public class ConditionDemo {
private final ReentrantLock lock = new ReentrantLock();
// 条件1:饭熟了
private final Condition riceReady = lock.newCondition();
// 条件2:菜好了
private final Condition dishReady = lock.newCondition();
// 等饭的线程
public void waitForRice() throws InterruptedException {
lock.lock();
try {
riceReady.await(); // 只等“饭熟”的信号,不关心“菜”
System.out.println("饭熟了,开始吃!");
} finally {
lock.unlock();
}
}
// 做饭的线程:饭熟了,只唤醒等饭的线程
public void cookRice() {
lock.lock();
try {
System.out.println("饭做好了!");
riceReady.signal(); // 精准唤醒等饭的线程,不打扰等菜的
} finally {
lock.unlock();
}
}
}
适用场景:生产者 - 消费者模型(比如多个生产者、多个消费者,精准唤醒对应角色)。
五、避坑指南:别被俩锁 “坑死”!
5.1 ReentrantLock的最大坑:忘了释放锁!
如果没把unlock()放在finally里,一旦临界区代码抛异常,锁就永远不会释放,其他线程全堵死 —— 相当于 “你出门没锁门,结果家里被搬空了”。
// 错误示范:没放finally,抛异常就gg
public void badGoHome() {
lock.lock();
int i = 1 / 0; // 抛异常!
lock.unlock(); // 这行永远执行不到,锁泄漏了!
}
// 正确示范:finally兜底,必释放锁
public void goodGoHome() {
lock.lock();
try {
int i = 1 / 0;
} finally {
lock.unlock(); // 就算抛异常,也会执行!
}
}
5.2 synchronized的坑:死等没商量!
如果一个线程持有synchronized锁,并且进入死循环(比如while(true)),其他线程想拿锁只能一直等,没有任何 “退出机制”—— 相当于 “大爷把门锁了,自己在里面睡大觉,外面的人永远进不来”。
这时ReentrantLock的tryLock()就能救场:“等 5 秒还没进去,就放弃”,避免线程无限等待。
5.3 公平锁别乱用:性能会下降!
ReentrantLock的公平锁虽然 “公平”,但需要维护一个排队队列,线程切换开销大 —— 相当于 “精英管家要一个个登记排队的人,效率变低”。非公平锁虽然可能 “插队”,但性能更高,大部分场景下非公平锁就够了。
六、选择建议:什么时候找大爷,什么时候找精英?
选synchronized(大爷)的场景:
- 简单同步场景:比如给对象字段加 1、判断变量是否为空,代码逻辑简单。
- 怕麻烦、不想写try-finally:担心自己忘了释放锁,交给 JVM 自动管理更放心。
- 低并发场景:比如日常开发中的简单同步,没有复杂需求(超时、中断、公平锁)。
一句话:复杂大活找精英,功能强大能搞定!
七、总结:俩锁的 “终极定位”
- synchronized:Java 并发的 “基础款锁”,语法简洁、安全可靠,是 “躺平式” 解决方案,覆盖 80% 的日常场景。
- ReentrantLock:Java 并发的 “进阶款锁”,功能强大、灵活可控,是 “内卷式” 解决方案,用于解决 20% 的复杂场景。
🎲synchronized是 “佛系锁”,能搞定就行,不多操心;
🚀 ReentrantLock是 “卷王锁”,不仅要搞定,还要搞定得漂亮、精准、高效!实际开发中,先看场景,再选锁 —— 简单活别 “杀鸡用牛刀”,复杂活别 “用菜刀砍钢筋”!


学习快乐!!!
2101

被折叠的 条评论
为什么被折叠?



