根本差异在于锁的获取与释放是否自动:synchronized是JVM隐式锁,自动加锁释放;Lock是JDK显式锁,需手动调用lock()和unlock(),支持tryLock()、可中断等待等高级特性,但必须在finally中释放以防死锁。

Lock接口和synchronized有什么本质区别
根本差异在于锁的获取与释放是否自动:synchronized 是 JVM 层面的隐式锁,进入同步块自动加锁、退出时自动释放(包括异常路径);而 Lock 是 JDK 提供的显式锁接口,必须手动调用 lock() 和 unlock(),不配对会导致死锁或资源泄漏。
这意味着你完全掌控锁的生命周期——可以尝试非阻塞获取(tryLock())、可中断等待(lockInterruptibly())、超时获取(tryLock(long, TimeUnit)),这些是 synchronized 做不到的。
常见错误现象:unlock() 忘写、写在 try 块里没进 finally、或者在构造异常后提前 return 导致跳过释放逻辑。
- 必须把
unlock()放在finally块中,且确保该块一定执行 - 不要在
lock()之前就声明变量并赋值,避免因构造失败导致锁未获取却误调unlock() -
ReentrantLock不是线程安全的,不能跨线程共享同一个实例来“协调”不同对象的锁
ReentrantLock基本用法与典型模板
最常用实现是 ReentrantLock,支持重入、公平/非公平模式。标准写法必须遵循“先 lock,再 try-finally-unlock”结构:
立即学习“Java免费学习笔记(深入)”;
private final Lock lock = new ReentrantLock();
public void doSomething() {
lock.lock();
try {
// 临界区操作
} finally {
lock.unlock(); // 即使抛异常也保证释放
}
}
注意:不能把 lock.lock() 放进 try,否则一旦加锁失败(如被中断),unlock() 会抛 IllegalMonitorStateException。
使用场景举例:缓存更新、计数器累加、多步状态变更需原子性保障的操作。
- 初始化时可传
true启用公平模式(FIFO 等待队列),但性能略低;默认是非公平,吞吐更高 - 非公平模式下,新线程可能插队成功,导致等待线程饥饿——这不是 bug,是设计取舍
- 若需条件等待(类似
wait()/notify()),要用lock.newCondition(),而非Object方法
tryLock() 的适用边界和陷阱
tryLock() 返回 boolean,表示是否成功获取锁。它不阻塞,适合“尽力而为”型逻辑,比如避免长等待、做快速失败决策。
典型错误是把它当 lock() 用却忽略返回值判断:
if (lock.tryLock()) {
try {
// 处理业务
} finally {
lock.unlock();
}
} else {
// 必须处理获取失败的情况:丢弃、降级、重试或抛异常
}
容易踩的坑:
-
tryLock()在公平模式下仍可能失败——即使队列为空,也可能因 CAS 竞争失败 - 带超时的
tryLock(100, TimeUnit.MILLISECONDS)若超时返回false,不代表锁被占用,可能是调度延迟或 GC 暂停 - 不要在循环里无休止调用
tryLock()而不 sleep,会空转消耗 CPU
LockSupport.park() 和 Condition.await() 别混用
LockSupport.park() 是底层线程阻塞原语,Condition.await() 是构建在 Lock 之上的高级等待机制,二者语义完全不同。
直接用 LockSupport 绕过 Lock 管理,等于放弃重入计数、条件队列、中断响应等保障。例如:
- 在
ReentrantLock持有状态下调LockSupport.park(),不会自动释放锁,其他线程永远拿不到 -
Condition.await()会自动释放锁,并在唤醒后重新竞争获取,这是它和park/unpark的关键分工 - 除非你在写 AQS 子类或极底层并发工具,否则不应直接碰
LockSupport
复杂点在于:Condition 的 signal() 不保证立即唤醒,只是把线程从等待队列移到同步队列,后续能否执行取决于锁竞争结果。这点常被忽略,导致“唤醒了但没执行”的错觉。










