文章目录
Java中的
Lock接口及其与
synchronized的区别:
1、显式锁定: Lock是一个接口,提供了比synchronized更灵活的锁定机制。
2、可中断锁定: Lock允许尝试非阻塞地获取锁,或者在锁定期间响应中断。
3、公平性选择: Lock提供了选择公平锁或非公平锁的能力。
4、性能差异: 在不同情况下,Lock和synchronized的性能表现有所不同。
一、核心比喻: 俩 “锁王” 的人设差异
先给它俩定个 “人设”,瞬间理解定位:
1. synchronized:小区门口的 “退休大爷保安”
- 大爷自带 “门禁系统”,你进门他自动拦着别人,你出门他自动放行,不用你操心。但大爷脾气倔,认死理,功能单一,你跟他讲道理(比如 “我等半天了,能先让我进吗?”),他一概不听,只认 “先到先得,要么等要么走”。
2. Lock:你家请的 “智能门锁 + 私人管家”(比如 ReentrantLock)
- 门锁是你自己装的,得手动刷卡(lock())进门,出门必须记得手动锁门(unlock()),忘了锁门就等于 “敞着门睡觉”。但管家超灵活,能帮你干好多大爷干不了的事:比如 “等 5 分钟没人来就别等了”(超时等待)、“有人插队就打断他”(可中断)、“按排队顺序来,不允许加塞”(公平锁)。
二、使用方式:一个 “全自动”,一个 “手动挡”
⬆️ 1. synchronized: 大爷帮你 “包办一切”
- 用 synchronized 就像走小区门禁,你只要往门口一站(进入同步方法 / 代码块),大爷自动锁门;你走了(方法 / 代码块结束),大爷自动开门,全程不用你动手。
// 同步方法:相当于“进小区大门,大爷直接拦着别人”
public synchronized void goHome() {
System.out.println("我进家门了,别人等着吧!");
}
// 同步代码块:相当于“进家门的客厅,大爷只拦客厅门口”
public void goLivingRoom() {
synchronized(this) { // this 就是“客厅门”
System.out.println("我进客厅了,别人别进来抢沙发!");
}
}
特点:语法简洁,“自动加锁 + 自动释放”,像 “全自动洗衣机”,不用管细节,但也没机会 “手动调程序”。
➡️ 2. Lock: 管家让你 “自己动手,丰衣足食”
- 用 Lock 就像用智能门锁,你得自己刷卡进门,出门必须手动锁门,还得把 “锁门” 这事记死 —— 万一忘了,管家可不会帮你补锁。
// 手动加锁+手动释放:少一步都不行!
Lock lock = new ReentrantLock(); // 装个智能门锁
public void goHome() {
lock.lock(); // 刷卡进门(手动加锁)
try {
System.out.println("我进家门了,别人等着吧!");
} finally {
lock.unlock(); // 出门必须锁门(手动释放),放finally里才保险!
}
}
特点:语法繁琐,但 “手动挡” 意味着 “可控性强”。就像开手动挡车,虽然麻烦,但能玩出 “漂移”(各种高级功能),而 synchronized 就是自动挡,只能乖乖开。
三、核心差异:大爷 vs 管家,能力天差地别
用一张 “人设对比表”,把差异怼到你眼前:
| 对比维度 | synchronized(退休大爷) | Lock(智能管家) | 形象比喻 |
|---|---|---|---|
| 实现层面 | JVM 内置实现(字节码 + 监视器锁),底层是 “黑盒” | Java 代码实现(基于条件队列),底层是 “白盒” | 大爷是 “体制内退休”,背后有人撑腰;管家是 “外包精英”,啥都敢干 |
| 锁释放方式 | 自动释放(方法 / 代码块结束时) | 必须手动释放(finally 中写 unlock()) | 大爷 “目送你进门”,自动关门;管家 “你走了不锁门,他就当没看见” |
| 可中断性 | 获取锁时不可中断(死等,除非线程被终止) | lockInterruptibly() 可中断(等半天没人来,能 “溜”) | 大爷:“你要么等我放行,要么滚,别跟我 bb”;管家:“等 5 分钟还没人,你先去买杯奶茶?” |
| 尝试获取锁 | 不支持(要么拿到,要么一直等) | tryLock() 可立即返回(拿不到就走),还能设超时 | 大爷:“没轮到你就站那别动!”;管家:“试一下门开不开,开不了我先去干别的” |
| 公平锁 | 不支持(谁挤得快谁进,不管排队) | 可选公平锁(new ReentrantLock(true),按顺序来) | 大爷:“内卷之王!谁猛谁先上”;管家:“排队买奶茶,先到先得,不允许加塞” |
| 多个条件变量 | 不支持(只有一个 “等待队列”) | 支持多个 Condition(能分 “客厅等”“卧室等”) | 大爷:“所有人都在大门外等!”;管家:“要等客厅的去客厅门口,等卧室的去卧室门口” |
| 性能 | JDK1.6 后优化很多(偏向锁、轻量级锁),低竞争下差不多 | 高并发场景更灵活,复杂场景性能更优 | 大爷 “退休后学了新技能”(优化后),简单活干得快;管家 “专业对口”,复杂活干得好 |
| 可重入性 | 可重入(比如递归调用同步方法,不会死锁) | 可重入(ReentrantLock 名字就带 “重入”) | 大爷认熟人:“你上次进过,这次还能进”;管家认钥匙:“你有钥匙,再开多少次都行” |
四、经典场景: 该找大爷还是管家?
✅ 找大爷(synchronized)的场景:
- 简单同步:比如 “给对象的某个字段加 1”“判断一个变量是否为空”。
- 怕麻烦:不想写 try-finally,怕忘了释放锁。
- 低并发:比如小区人少,没人抢门禁,大爷慢悠悠也能搞定。
例子:家庭群抢红包,就用大爷看着,谁先点到红包谁拿,简单粗暴。
⬆️ 找管家(Lock)的场景:
- 需要 “超时等待”:比如 “尝试获取锁 5 秒,拿不到就放弃,别死等”。
- 需要 “公平排队”:比如银行办理业务,必须按取号顺序来,不能让 “插队党” 得逞。
- 需要 “多条件等待”:比如线程 A 等 “客厅没人”,线程 B 等 “卧室没人”,得分开等。
- 高并发复杂场景:比如电商秒杀,既要控制并发量,又要处理 “有人超时放弃” 的情况。
例子:双十一抢购,管家先让大家排队(公平锁),等 10 秒还没抢到的就提示 “手慢了”(超时),有人中途退出(中断)就把名额让给下一个,效率超高。
五、常见坑: 别被 “锁” 坑死!
🚀 1. Lock 的最大坑:忘了释放锁!
🚀 1. Lock 的最大坑:忘了释放锁!
🚀 1. Lock 的最大坑:忘了释放锁!
🚀 1. Lock 的最大坑:忘了释放锁!
🚀 1. Lock 的最大坑:忘了释放锁!
🚀 1. Lock 的最大坑:忘了释放锁!
如果不用 finally 包 unlock(),一旦临界区代码抛异常,锁就永远不会释放,其他线程全堵死 —— 相当于 “你出门没锁门,结果家里被搬空了”。
// 错误示范:没放finally,抛异常就gg
public void goHome() {
lock.lock();
if (1 / 0 == 0) { // 抛异常了!
System.out.println("永远到不了这里");
}
lock.unlock(); // 这行代码永远执行不到,锁没释放!
}
// 正确示范:finally 兜底,必释放锁
public void goHome() {
lock.lock();
try {
if (1 / 0 == 0) { ... }
} finally {
lock.unlock(); // 就算抛异常,也会执行!
}
}
➡️ 2. synchronized 的坑:死等没商量!
如果一个线程持有 synchronized 锁,另一个线程想获取,只能死等 —— 就算等 1 小时,也不能 “中途放弃”。比如:
// 线程A持有锁,一直不释放(比如死循环)
public synchronized void holdLock() {
while (true) { // 死循环,锁永远不释放
System.out.println("我就不放手!");
}
}
// 线程B想获取锁,只能一直等,直到线程A被终止
public synchronized void waitLock() {
System.out.println("我等到花儿都谢了!"); // 永远到不了这里
}
而 Lock 可以用 tryLock(5, TimeUnit.SECONDS),等 5 秒没拿到就走,不会死等。
六、总结:一句话选对 “锁王”
- 简单活找大爷:synchronized 语法简洁,不易出错,适合简单同步场景(90% 的日常开发够用了)。
- 复杂活找管家:Lock 功能强大,灵活可控,适合高并发、需要超时 / 公平锁 / 多条件的场景(比如框架、中间件开发)。
结论: synchronized 是 “躺平式锁王”,能自动搞定就绝不麻烦你;Lock 是 “内卷式锁王”,你给它指令,它能帮你卷死所有竞争对手!

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



