
synchronized 与 ReentrantLock 不该靠背关键词二选一,本文用 4 个面试场景讲清可重入性、超时与中断获取、Condition 条件队列和公平锁边界,帮你把 API 区别答成并发设计判断。
两者都能做互斥,ReentrantLock 和 synchronized 的基本行为与语义一致,也都支持同一线程重入。synchronized 使用对象的隐式监视器,锁的获取和释放受代码块范围管理;ReentrantLock 需要显式调用 lock 和 unlock,但因此可以增加 tryLock、可中断获取、超时等待、公平策略和多个 Condition。选择时先看业务是否需要这些控制能力,不要直接下结论说谁天然更快。
public synchronized void increase() {
count++;
}private final Object monitor = new Object();
public void increase() {
synchronized (monitor) {
count++;
}
}Lock 文档把这种特征称为 block-structured locking:锁的获取和释放都落在明确的代码范围里,也因此更容易避免忘记释放的错误。1ReentrantLock,规范写法必须把 unlock() 放进 finally:private final ReentrantLock lock = new ReentrantLock();
public void increase() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}ReentrantLock 的官方文档也直接给出了这种 before/after 结构。它的价值不是让每个计数器都换一种写法,而是当你确实需要「怎么等、等多久、等不到怎么办」时,提供更细的控制。2synchronized 往往更省心。ReentrantLock。lock() 获取不到锁时会继续等待。若业务有时间预算,面试官可能会追问:线程一直排队,超时怎么办?tryLock:if (lock.tryLock(200, TimeUnit.MILLISECONDS)) {
try {
updateInventory();
} finally {
lock.unlock();
}
} else {
recordTimeout();
}tryLock(long, TimeUnit) 在规定时间内拿到锁就返回 true,等待时间耗尽则返回 false;等待期间被中断,会抛出 InterruptedException。无参 tryLock() 则是不等待的立即尝试。1lockInterruptibly():try {
lock.lockInterruptibly();
try {
callRemoteService();
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}tryLock() 不是「等一会儿」。无参版本拿不到就立即返回 false,限时版本才会等待。InterruptedException 后,不要静默吞掉中断。示例里恢复中断标记,是为了让上层仍能看到取消信号;具体是返回、重试还是结束任务,要按业务语义决定。Lock 相比 synchronized 的扩展能力。面试官真正想听的不是方法名清单,而是你能否把「超时」「取消」和「释放锁」放到同一条控制链里。1private final ReentrantLock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public void put(Item item) throws InterruptedException {
lock.lockInterruptibly();
try {
while (queue.isFull()) {
notFull.await();
}
queue.add(item);
notEmpty.signal();
} finally {
lock.unlock();
}
}while 不能换成 if。Condition.await() 允许虚假唤醒,线程被唤醒也不代表队列此刻一定有空间;线程重新拿到锁后,必须再次检查条件。Oracle 的 Condition 文档明确建议始终在循环中测试状态谓词,并说明 await() 会原子地释放关联锁,返回前重新获取这把锁。3Condition 的实用区别是:同一把 Lock 可以关联多个等待集合。生产者只在 notFull 上等待,消费者只在 notEmpty 上等待,状态改变后再唤醒对应一侧。它不是一个脱离锁独立工作的「通知器」,创建它要用 lock.newCondition(),调用 await()、signal() 或 signalAll() 也要遵守关联锁的使用约束。3synchronized 的对应关系说清楚:| 隐式监视器 | 显式锁 |
|---|---|
synchronized | lock.lock() / unlock() |
wait() | condition.await() |
notify() / notifyAll() | signal() / signalAll() |
| 一个对象的监视器等待集合 | 可以给同一把锁建立多个 Condition |
Object.wait() 要求当前线程先拥有对象监视器,等待时释放该对象上的同步占有,并且同样可能因通知、中断、超时或伪唤醒而返回,所以也要用 while 复查条件。4new ReentrantLock(true) 表示公平策略。竞争时,它倾向于把锁交给等待时间更长的线程;官方文档同时提醒,公平锁通常会牺牲一部分吞吐量,而且锁的公平不等于线程调度公平。ReentrantLock fairLock = new ReentrantLock(true);
boolean acquired = fairLock.tryLock();| 需求 | 更应该讲什么 | 不要怎么答 |
|---|---|---|
| 普通互斥、临界区短 | synchronized 的结构清楚、释放风险低 | 「ReentrantLock 一定更快」 |
| 等锁要超时 | tryLock(timeout),超时走降级或返回 | 把无参 tryLock() 说成限时等待 |
| 等锁要能取消 | lockInterruptibly() 与中断处理 | 捕获异常后什么都不做 |
| 多种状态分别等待 | 一把锁配多个 Condition,用 while 复查 | await() 外只写 if |
| 竞争顺序有要求 | 公平策略的吞吐与等待时间取舍 | 说公平锁能保证线程轮流执行 |
我不会先按 API 名称二选一,而是先看锁的需求。如果只是保护一个边界清楚的临界区,我优先考虑 synchronized,减少手动释放带来的风险。如果需要超时、可中断获取,或者同一把锁下有多个独立等待条件,我会用 ReentrantLock 和对应的 Condition。无论选哪种方案,我都会确认锁的持有范围、异常路径、等待条件和监控信号,避免只看代码能不能编译。
ReentrantLock 的 unlock() 要放进 finally?await() 释放锁、返回前重新获取锁,以及为什么必须循环检查条件?Condition 的场景?tryLock() 可能插队?docs.oracle.com
docs.oracle.com
docs.oracle.com
docs.oracle.com
This story was produced automatically by a channel. One sentence is all it takes for Neodrop to keep producing for you.