可以先记一个结论:
简单同步,优先 synchronized;需要高级锁能力,选 ReentrantLock。
一、先说本质区别
synchronized
这是 Java 内置的监视器锁,写法简单,语法级支持:
synchronized (lock) {
// 临界区
}
或者修饰方法:
public synchronized void test() {
}
特点是:
- 写法最简单
- JVM 层面支持,出了作用域会自动释放锁
- 可重入
- 从 JDK 1.6 以后做了很多优化,性能已经很好
- 不能灵活中断、超时、轮询获取锁
ReentrantLock
这是 java.util.concurrent.locks 包里的显式锁:
Lock lock = new ReentrantLock();
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
特点是:
-
也是可重入锁
-
需要手动释放锁,必须放在
finally -
功能更强,支持:
tryLock():尝试获取锁tryLock(timeout):超时等待lockInterruptibly():可中断获取锁- 可选公平锁
Condition多条件队列
二、怎么选:直接按场景判断
1. 大多数业务代码:优先 synchronized
如果你只是做这种事情:
- 方法加锁
- 代码块互斥
- 不需要超时、中断、公平性
- 逻辑比较简单
那就优先用 synchronized。
因为它:
- 代码短
- 不容易忘记释放锁
- 可读性更好
- 出错概率更低
例如:
public synchronized void add() {
count++;
}
或者:
private final Object lock = new Object();
public void add() {
synchronized (lock) {
count++;
}
}
这类场景完全够用。
2. 需要更强控制能力:选 ReentrantLock
当你有下面这些需求时,synchronized 就不够用了:
① 需要尝试获取锁,拿不到就放弃
比如避免线程一直阻塞:
if (lock.tryLock()) {
try {
// 获取到锁再执行
} finally {
lock.unlock();
}
} else {
// 获取失败,走降级逻辑
}
synchronized 做不到。
② 需要可中断地等待锁
比如线程在排队等锁时,希望能响应中断:
lock.lockInterruptibly();
try {
// 业务逻辑
} finally {
lock.unlock();
}
synchronized 在等待锁时不能响应中断。
③ 需要超时机制
比如最多等 2 秒,还拿不到锁就返回失败:
if (lock.tryLock(2, TimeUnit.SECONDS)) {
try {
// 处理业务
} finally {
lock.unlock();
}
} else {
// 超时处理
}
synchronized 也做不到。
④ 需要多个条件队列
ReentrantLock 可以配合多个 Condition,做更精细的线程唤醒控制。
例如生产者消费者,不同线程等待不同条件:
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();
而 synchronized 只有一个隐式条件队列,只能配 wait/notify/notifyAll,控制粒度较粗。
⑤ 需要公平锁
Lock fairLock = new ReentrantLock(true);
公平锁会尽量按等待顺序获取锁。
不过要注意:
- 公平锁吞吐量通常更低
- 不是所有场景都值得用
三、性能上怎么理解
很多人以前会说:ReentrantLock 比 synchronized 性能好。
这个结论放到现在已经不准确了。
从 JDK 1.6 开始,synchronized 有了很多优化,比如:
- 偏向锁(历史上)
- 轻量级锁
- 锁粗化
- 锁消除
- 自适应自旋
所以在现代 JDK 中:
- 低竞争或普通竞争场景下,
synchronized和ReentrantLock性能差距通常没那么大 - 高并发复杂场景下,
ReentrantLock因为功能更多,有时更容易调优 - 真正决定性能的往往不是“用哪把锁”,而是锁粒度、竞争程度、临界区大小
所以不要为了“可能快一点”就默认上 ReentrantLock。
四、可维护性上谁更好
通常是 synchronized 更安全、更容易维护。
因为:
synchronized
- 自动释放锁
- 少写模板代码
- 不容易遗漏
ReentrantLock
- 必须手动
unlock() - 一旦忘记释放,容易死锁
错误示例:
lock.lock();
doSomething();
if (error) {
return; // 忘记 unlock
}
lock.unlock();
正确示例:
lock.lock();
try {
doSomething();
} finally {
lock.unlock();
}
所以如果不需要高级能力,优先 synchronized 更稳。
五、面试里常见回答模板
synchronized和ReentrantLock都是可重入的互斥锁。
一般情况下,如果只是做简单的同步控制,我会优先选择synchronized,因为语法简单,JVM 原生支持,自动释放锁,可读性和安全性更好。
如果业务需要更灵活的锁控制,比如可中断获取锁、超时获取锁、尝试加锁、公平锁,或者配合多个Condition做精细化线程协作,我会选择ReentrantLock。
在现代 JDK 中,两者性能差距通常不是首要考虑因素,更关键的是锁竞争程度、锁粒度和临界区设计。
六、一句话记忆
能用 synchronized 解决,就先用 synchronized;
只有当你明确需要 ReentrantLock 的高级特性时,再用 ReentrantLock。
七、给你一个选择表
选 synchronized
- 临界区简单
- 只需要互斥
- 想降低代码复杂度
- 更看重稳定和可维护性
选 ReentrantLock
- 需要
tryLock - 需要超时等待
- 需要可中断等待
- 需要公平锁
- 需要多个
Condition