Java synchronized 和 Lock 该怎么选
synchronized 和 Lock 都是 Java 里加锁的手段,一个是关键字一个是接口,功能重叠又各有脾气。这篇讲清它俩的区别:谁自动释放、谁能中断、谁能读写分离,再说说什么场景用哪个,帮你别再纠结到底选哪个。
写 Java 多线程,绕不开加锁。而一说加锁,就有个老问题冒出来:synchronized 和 Lock,到底用哪个?
网上的答案两极分化,有人说 synchronized 简单够用,有人说 Lock 才是高级玩法。
我自己踩下来的体会是:它俩没有绝对的谁好谁坏,是看场景。这篇就把区别和选择讲明白。
先说清楚,标题里那个 synchronized 是 Java 的关键字;Lock 是 java.util.concurrent.locks 包里的一个接口,最常用的实现是 ReentrantLock。
它俩各是什么来头
synchronized 是关键字,Java 语言自带的,JVM 层面帮你实现的锁。
用起来极简单,直接把要保护的代码框起来:
// 锁住这段代码,同一时刻只有一个线程能进
synchronized (lockObj) {
// 操作共享数据
balance = balance - 100;
}
Lock 是接口,属于 JDK 后来提供的并发工具,得手动 new 一个实现类出来用:
Lock lock = new ReentrantLock();
lock.lock(); // 手动上锁
try {
balance = balance - 100;
} finally {
lock.unlock(); // 手动释放,必须放 finally 里
}
光看这两段,第一个区别就出来了。
区别一 释放锁谁操心
synchronized 的锁是自动释放的。
代码块执行完,或者中间抛异常了,JVM 都会帮你把锁放掉。你不用管,省心。
Lock 就得自己动手释放。
你 lock() 了,就必须记得 unlock()。而且这个 unlock 一定要放在 finally 里——万一中间抛异常,finally 保证锁能被释放。
忘了写 unlock,或者没放 finally 结果异常把它跳过了,这把锁就永远不释放了,其他线程全卡死在那等着。这是用 Lock 最经典的坑。
所以这一局:synchronized 省心,Lock 灵活但要你自己兜底。
区别二 能不能不死等
这是 Lock 真正的看家本领。
用 synchronized,一个线程拿不到锁,就只能傻等,一直等到拿到为止,中途没法反悔,也问不了“还要等多久”。
Lock 就聪明多了,它给了你好几种“不死等”的选择:
tryLock():试一下,拿得到就锁,拿不到马上返回,我不等了,先去干别的tryLock(时间):最多等这么久,超时还没拿到就放弃lockInterruptibly():等锁的过程中,允许被别的线程中断掉
举个例子,抢不到锁就直接走人:
if (lock.tryLock()) { // 试着抢锁
try {
// 抢到了才干活
} finally {
lock.unlock();
}
} else {
// 没抢到,不干等,去做别的
}
这种能力在实际项目里很有用。比如避免死锁、比如做个“抢不到就降级”的兜底逻辑,synchronized 干不了这些。
区别三 读写能不能分开
还有个场景 synchronized 处理得比较糙。
很多数据是读多写少的。读操作之间其实不冲突,几个线程一起读没问题,只有写的时候才需要互斥。
但 synchronized 一刀切,不管读还是写,一次只放一个线程进去。几个线程明明只是想读一下,也得排队,白白浪费并发。
Lock 这边有个 ReentrantReadWriteLock,把锁拆成读锁和写锁:
- 读锁:多个线程可以同时拿,一起读
- 写锁:独占,写的时候谁都别进
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读的时候用读锁,允许多个线程并发读
rwLock.readLock().lock();
try {
// 读共享数据
} finally {
rwLock.readLock().unlock();
}
读多写少的场景,读写锁能把并发度拉上来一大截,这是 synchronized 给不了的。
顺带说说公平不公平
还有个小差别,锁的“公平性”。
- synchronized 是非公平的,谁抢到算谁的,不排队论资排辈
- Lock 可以选,new ReentrantLock(true) 就是公平锁,先来的先拿
公平锁看着更“讲道理”,但维护排队顺序有额外开销,吞吐量反而低。
所以多数时候非公平就够了,除非你真的需要严格的先来后到。这个点了解一下即可,不用纠结。
那到底怎么选
绕回最开始那个问题。我的判断很简单:
能用 synchronized 就用 synchronized。
它简单、不容易出错、锁自动释放,JDK 这些年也把它优化得很好,性能早就不是短板了。日常大部分加锁场景,它完全够用。
只有当 synchronized 满足不了时,才换 Lock。 什么时候满足不了?
- 需要尝试加锁、超时放弃、可中断 → 用 Lock 的 tryLock 那套
- 读多写少,想让读操作并发 → 用读写锁
- 需要公平锁 → 用 ReentrantLock 的公平模式
一句话:synchronized 是默认选项,Lock 是“需要高级功能时”的进阶选项。别一上来就为了显得高级硬上 Lock,结果 unlock 忘了写,得不偿失。
小结
把这俩兄弟捋一遍,其实分工挺清楚。
- 释放:synchronized 自动放,Lock 手动 unlock(记得放 finally)
- 等待:synchronized 只能死等,Lock 能 tryLock、超时、可中断
- 读写:synchronized 一刀切,Lock 有读写锁能分离
- 公平:synchronized 非公平,Lock 可选公平
选择的原则更简单:默认 synchronized,需要高级能力再上 Lock。
没有哪个绝对更好,合适的才是好的。
搞清楚它俩各自的脾气,选起来就不慌了。
