一、形象比喻:为什么说 synchronized 是自动挡?
想象一下,你开着一辆自动挡汽车:踩油门就走,踩刹车就停,无需关心换挡------一切交给系统自动处理。这就像 synchronized,简单易用,由 JVM 帮你搞定锁的获取和释放,几乎不会出错。
而 Lock 就像手动挡汽车:你需要手动挂挡、踩离合,虽然操作复杂,但能在复杂路况下获得更好的控制------比如爬坡时降挡提高扭矩,高速时升挡节省油耗。
二、核心区别:一张表看懂两者差异
维度 | synchronized | Lock(以 ReentrantLock 为例) |
---|---|---|
出身 | Java 关键字,JVM 底层实现 | 接口(java.util.concurrent.locks.Lock),代码实现 |
锁管理 | 自动获取/释放(进同步块加锁,出同步块解锁) | 手动获取(lock())/释放(unlock()),需在 try-finally 中释放 |
公平性 | 只能是非公平锁(允许插队) | 支持公平/非公平锁(构造函数参数控制) |
可中断性 | 不可中断(一旦阻塞只能干等) | 可中断(lockInterruptibly() 方法) |
超时控制 | 不支持(无限等待) | 支持(tryLock(timeout) 方法) |
可重入性 | 可重入(自动记录锁次数) | 可重入(需手动实现,ReentrantLock 已支持) |
条件变量 | 仅支持 wait()/notify(),功能有限 | 支持多条件变量(Condition 接口) |
性能 | JDK 1.6 后优化(偏向锁/轻量级锁),接近 Lock | 高并发下略优,尤其非公平锁场景 |
三、通俗解释:关键差异的 "人话" 版
1. 锁的管理:"自动门" vs "手动门"
synchronized 就像商场的自动门:
java
synchronized (obj) { // 走进门,自动上锁
// 临界区代码(购物中)
} // 走出门,自动解锁
你无需关心门何时开关,系统全帮你搞定。
Lock 则像你家的防盗门:
java
Lock lock = new ReentrantLock();
lock.lock(); // 用钥匙开门(手动加锁)
try {
// 临界区代码(在家活动)
} finally {
lock.unlock(); // 出门前必须手动锁门!
}
如果忘记锁门(没在 finally 里调用 unlock()),别人可能趁机闯入(死锁风险)。
2. 公平性:"插队文化" vs "排队礼仪"
synchronized 是非公平锁:就像买奶茶时,新到的人可能直接凑到柜台前问"我的好了吗",如果刚好你的奶茶好了,他可能就直接拿走了------不管队伍里有没有人在等。
Lock 可以选择公平锁:new ReentrantLock(true) 就像排队时有人维持秩序,新到的人必须排到队尾,严格按"先来后到"的顺序服务,更公平但效率略低。
3. 灵活性:"功能机" vs "智能手机"
synchronized 像功能机:只能打电话、发短信,功能简单但够用。
Lock 像智能手机,支持各种高级功能:
- 超时放弃:等了 1 秒还没抢到锁,就放弃(tryLock(1, TimeUnit.SECONDS)),避免傻等。
- 可中断:等锁时如果收到中断信号(比如 thread.interrupt()),可以停止等待去处理其他事情。
- 多条件等待:一个锁可以关联多个等待队列(Condition),实现精细控制(比如读写锁的功能)。
4. 性能:"优化后逆袭" vs "高并发优势"
JDK 1.6 前,synchronized 是重量级锁,性能比 Lock 差很多。 JDK 1.6 后,synchronized 引入了偏向锁 、轻量级锁等优化,性能大幅提升,普通场景下和 Lock 差不多。 但在高并发、锁竞争激烈时,Lock 的非公平锁因为减少了线程切换开销,吞吐量通常更高。
四、使用场景:什么时候该选哪个?
选 synchronized 当:
- 需求简单(比如简单的同步代码块),希望代码简洁。
- 不想手动管理锁(怕忘记释放锁导致死锁)。
- 不需要高级功能(公平性、超时、中断等)。
选 Lock 当:
- 需要公平锁(必须按顺序执行,防止饥饿)。
- 需要超时控制(避免线程无限等待)。
- 需要可中断(等待锁时能响应中断信号)。
- 需要多条件等待(精细控制线程间协作)。
五、总结:没有最好,只有最合适
synchronized 是 "简单省心" 的选择,适合大多数基础同步场景;Lock 是 "灵活可控" 的选择,适合复杂场景。 就像开车:新手可能更喜欢自动挡的简单,但老司机在复杂路况下会更倾向于手动挡的控制感。Java 同步也是如此------理解两者的差异,才能在不同场景下做出最合适的选择。如果用 Lock,一定要在 finally 中释放锁,否则可能导致死锁!