Lock和synchronized是同一机制的两种实现路径:前者是JDK提供的需手动管理的接口,后者是JVM优化的语法糖;简单场景优先用synchronized,复杂并发控制必须用Lock。

锁(Lock)和同步块(synchronized block)根本不是同一层的东西
同步块是语法糖,是JVM内置的、基于监视器锁(monitor)的快捷写法;而Lock(如ReentrantLock)是JDK提供的接口,需要你手动调用lock()和unlock()。它们不是“两种锁”,而是“一种机制的两种实现路径”——就像for循环和Stream.forEach()都能遍历集合,但底层控制权完全不同。
什么时候必须用Lock,不能用synchronized?
当你的并发逻辑超出“简单互斥”时,synchronized就力不从心了。以下场景Lock不可替代:
- 需要超时获取锁:用
tryLock(1, TimeUnit.SECONDS)避免无限等待 - 线程正在等锁时要响应中断:用
lockInterruptibly(),而synchronized会无视Thread.interrupt() - 要为不同条件单独挂起/唤醒线程:用
Lock.newCondition()创建多个Condition,而不是被wait()/notify()绑死在单个对象上 - 明确要求公平性:构造
new ReentrantLock(true),而synchronized永远是非公平的
同步块为什么还值得优先用?
它快、稳、省心——尤其在低竞争、逻辑简单的临界区。JVM对synchronized做了大量优化(偏向锁、轻量级锁、锁消除),很多场景下性能反超Lock。更重要的是:
- 不会漏释放:JVM保证退出同步块时自动释放锁,哪怕抛异常
- 不会死锁于忘记
unlock():而Lock若漏写finally { lock.unlock(); },资源就永久卡死 - 锁对象更可控:用
private final Object lock = new Object();做同步块锁,比用this或getClass()安全得多
最容易踩的坑:混用锁对象或误判锁粒度
比如写了个synchronized实例方法,以为“所有线程都串行”,结果发现并发没挡住——其实是多个对象实例各自持有一把this锁,互不干扰。又或者用String或Integer作同步块锁对象,触发了常量池复用,导致意外的跨实例锁竞争。
立即学习“Java免费学习笔记(深入)”;
真实建议:
- 保护对象内部状态 → 用
synchronized实例方法 或 同步块锁this(确保是同一个实例) - 保护类级别共享数据(如静态计数器)→ 用
synchronized静态方法 或 同步块锁MyClass.class - 需要精细控制或高级语义 → 改用
ReentrantLock,且务必套try-finally - 别拿
lock变量本身做同步块锁对象——它只是个引用,不是锁的载体
最常被忽略的一点:无论是synchronized还是Lock,它们提供的不只是互斥,更是happens-before关系——这意味着进入/退出时强制刷新主内存变量值。这点一旦忘掉,光加锁也挡不住可见性问题。










