一、底层实现层面

  1. synchronized JVM 底层基于对象 Monitor 监视器,依赖操作系统内核互斥锁;JDK1.6 做锁升级:偏向锁→轻量级锁→重量级锁; 修饰代码块靠 monitorenter/monitorexit 字节码,方法靠 ACC_SYNCHRONIZED 标记。
  2. Lock(以 ReentrantLock 为例) Java 语言层面接口,底层基于 AQS 抽象队列同步器,用 CAS + CLH 双向等待队列实现,全程用户态自旋,竞争激烈才阻塞。

二、使用方式:隐式锁 vs 显式锁

  1. synchronized:自动加解锁,无需手动操作
    • 同步方法 / 同步代码块,代码执行完毕、抛出异常都会自动释放锁,不会死锁;
    synchronized (obj) {
        // 自动释放
    }
    
  2. Lock:手动加锁、手动解锁,必须在 finally 中释放,否则异常会永久持有锁,死锁
    lock.lock();
    try {
        // 业务逻辑
    } finally {
        lock.unlock(); // 必须手动释放
    }
    

三、灵活性与丰富功能(Lock 核心优势)

1. 可中断等待

  • synchronized:线程阻塞等待锁时,无法被中断,只能死等;
  • Lock:lockInterruptibly(),等待过程中可响应 interrupt(),抛出中断异常,方便取消任务。

2. 支持超时等待

  • synchronized:没有超时机制,拿不到锁无限阻塞;
  • Lock:tryLock(long time, unit) 指定等待时间,超时拿不到直接返回 false,避免死锁。

3. 非阻塞尝试获取锁

tryLock() 立刻尝试,拿到返回 true,拿不到直接返回,不阻塞线程。

4. 公平锁 / 非公平锁自由切换

  • synchronized:永远是非公平锁;
  • ReentrantLock 构造参数可指定:new ReentrantLock (true) 公平锁,false 非公平(默认)。

5. 多条件变量 Condition

  • synchronized 只能配合对象 wait/notify/notifyAll,一个锁只能对应一个等待队列;
  • Lock 可创建多个 Condition,精准分组等待、精准唤醒,生产者消费者场景非常好用,区分 “队满”“队空” 两组等待线程。

四、锁类型:可重入性

两者都是可重入锁:同一线程多次获取同一把锁不会阻塞。

  • synchronized:Monitor 内部计数器记录重入次数;
  • ReentrantLock:AQS 的 state 变量记录重入次数。

五、性能表现

  1. JDK1.6 之前:synchronized 重量级锁,内核切换开销大,性能远差于 Lock;
  2. JDK1.6 及以后:引入偏向锁、轻量级锁自旋优化,无竞争场景两者性能几乎持平;
  3. 高并发激烈竞争场景:Lock 自旋优化、灵活中断 / 超时设计,吞吐量通常优于 synchronized。

六、锁释放规则

  1. synchronized:自动释放,正常结束 / 抛异常都会释放,不会漏;
  2. Lock:必须手动 unlock,代码异常忘记写 finally 释放,锁永久占用,其他线程永久阻塞。

七、适用场景总结

  1. 简单同步逻辑、代码量少、低并发场景:优先 synchronized,写法简洁不易出错;
  2. 需要中断等待、超时放弃、公平锁、多条件精准唤醒、高并发场景:使用 Lock。

精简一句话总结

synchronized 是 JVM 隐式锁,自动释放、使用简单,功能单一;Lock 是 API 显式锁,底层 AQS 实现,支持中断、超时、公平锁、多 Condition,灵活性更强,但需要手动在 finally 解锁。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐