ReentrantLock 与 synchronized对比

以下是 ReentrantLocksynchronized 的全面对比解析,涵盖实现机制、特性差异、性能演进、选型指南及代码示例。


ReentrantLock vs synchronized 完全对比手册


一、核心实现机制对比

维度 synchronized ReentrantLock
实现层级 JVM 内置(C++ 实现,依赖对象头 Mark Word 和 Monitor) JDK 层 API(Java 实现,基于 AQS 框架)
锁状态存储 对象头中的 Mark Word(记录锁状态、持有线程、重入次数等) AQS 的 volatile int state 字段
等待队列 每个对象关联一个 Monitor ,内部维护 _cxq_EntryList_WaitSet 三个队列 AQS 内部维护一个 FIFO 的 CLH 变种队列
条件变量 单个隐式条件队列(wait/notify/notifyAll 支持多个 Condition 对象,每个维护独立条件队列
锁升级机制 偏向锁 → 轻量级锁 → 重量级锁(JDK 6+ 优化) 无锁升级,直接基于 CAS 和队列实现重量级互斥
释放方式 自动释放(代码块结束或异常) 必须显式释放unlock() 通常在 finally 中)

二、特性功能全面对比

功能特性 synchronized ReentrantLock 说明
可重入性 ✅ 支持 ✅ 支持 同一线程可重复获取已持有的锁
公平锁 ❌ 仅非公平 ✅ 构造函数指定 true 为公平锁 公平锁保证 FIFO 顺序,但吞吐量低
可中断获取 ❌ 阻塞时不可中断 lockInterruptibly() 等待锁时可响应 Thread.interrupt()
超时获取 ❌ 不支持 tryLock(timeout, unit) 允许在指定时间内尝试获取锁
非阻塞尝试 ❌ 不支持 tryLock() 立即返回,不排队等待
多条件等待 ❌ 仅单个隐式条件 ✅ 多个 Condition 可实现精确唤醒,避免惊群效应
状态监控 ❌ 无直接 API getQueueLength()hasQueuedThreads() 便于运维监控和性能调优
锁降级/升级 ❌ 不支持 ❌ 不支持 两者均不直接支持锁降级

三、底层源码实现对比

1. synchronized 底层(JVM 层面)
cpp 复制代码
// 简化示意:synchronized 代码块编译后对应 monitorenter / monitorexit 指令
// 实际 JVM 实现涉及 ObjectMonitor 结构

class ObjectMonitor {
  _header       = NULL;          // 对象头备份
  _count        = 0;             // 重入次数
  _waiters      = 0;             // 等待线程数
  _recursions   = 0;             // 递归次数(同 count)
  _owner        = NULL;          // 持有线程
  _WaitSet      = NULL;          // wait() 线程队列
  _EntryList    = NULL;          // 阻塞等待锁的线程队列
  ...
};

// 进入同步块:monitorenter
// 1. 如果当前对象无锁(偏向/轻量级未膨胀),尝试 CAS 获取偏向锁/轻量级锁
// 2. 失败则膨胀为重量级锁,进入 _EntryList 等待
// 3. 重入时直接递增 _recursions

// 退出同步块:monitorexit
// 1. 递减 _recursions,若归零则释放锁
// 2. 若 _EntryList 不为空,唤醒队首线程
2. ReentrantLock 底层(AQS 层面)
java 复制代码
// 核心:基于 AQS 的 state 变量和 CLH 队列

public class ReentrantLock implements Lock {
    private final Sync sync;
    
    abstract static class Sync extends AbstractQueuedSynchronizer {
        // 重入计数 state
        // state == 0: 锁空闲
        // state > 0: 锁被持有,值为重入次数
        
        // 释放锁钩子
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    }
    
    // 非公平锁获取逻辑
    static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))   // 第一次插队
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);                 // 进入 AQS 排队
        }
        
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires); // 第二次插队机会
        }
    }
}

四、性能演进与现状

JDK 版本 synchronized 性能状态 ReentrantLock 性能状态 建议
JDK 1.5 之前 纯重量级锁,每次加锁都涉及系统调用,性能极差 尚未引入(java.util.concurrent 包在 1.5 发布) 无选择余地
JDK 1.5 重量级锁 刚推出,基于 AQS,性能远优于 synchronized 优先使用 ReentrantLock
JDK 1.6 引入偏向锁、轻量级锁、自旋锁优化,性能大幅提升 稳定,性能优秀 两者性能接近,按功能需求选择
JDK 1.8+ 持续优化(如锁消除、锁粗化),低竞争下几乎无开销 性能稳定 低竞争无差别 ,高竞争 ReentrantLock 略优(但差距很小)

当前结论 :在大多数常见场景下,两者性能处于同一量级 ,不应再将性能作为首选区分因素。应基于功能需求代码安全性做出选择。


五、代码风格与安全性对比

对比点 synchronized ReentrantLock
代码简洁性 ⭐⭐⭐⭐⭐ 无需手动释放,代码块即锁范围 ⭐⭐ 必须显式 lock()/unlock()
异常安全性 ⭐⭐⭐⭐⭐ 自动释放,无死锁风险 ⭐⭐⭐ 若忘记 finally 释放,死锁
灵活控制 ⭐⭐ 无法中断、超时、非阻塞 ⭐⭐⭐⭐⭐ 提供多种锁获取方式
条件同步 ⭐⭐ 只能 wait/notify 在单个对象上 ⭐⭐⭐⭐⭐ 多 Condition 精准控制
可调试性 ⭐⭐⭐ 难以查询锁状态 ⭐⭐⭐⭐ 提供 getQueueLength() 等监控 API

示例对比

java 复制代码
// synchronized 风格 ------ 简单安全
synchronized (lock) {
    doSomething();
}

// ReentrantLock 风格 ------ 灵活但需谨慎
lock.lock();
try {
    doSomething();
} finally {
    lock.unlock();
}

六、典型场景选型指南

优先使用 synchronized 的场景
  1. 简单互斥:仅需保护临界区,无特殊功能要求。
  2. 方法级同步 :直接在方法上使用 synchronized 关键字。
  3. 团队规范 :团队对 ReentrantLock 不熟悉,易用错导致死锁。
  4. 性能非瓶颈:临界区执行时间极短,锁竞争不激烈。
必须使用 ReentrantLock 的场景
场景 所需特性 示例
需要超时放弃 tryLock(timeout) 获取数据库连接,等待 500ms 无果则降级返回缓存
需要响应中断 lockInterruptibly() 用户点击取消,立即终止等待锁的线程
需要公平锁 new ReentrantLock(true) 任务调度系统严格按提交顺序执行
需要多条件精准唤醒 多个 Condition 有界阻塞队列,生产者/消费者分别唤醒对方
需要监控锁状态 getQueueLength() 运维监控系统,告警等待线程数过多
复杂锁交互 非阻塞尝试 tryLock() 避免死锁,尝试获取多个锁

七、混合使用注意事项

  1. 不要混用:同一个共享资源的同步不要混用两种锁机制,极易出错。
  2. synchronized 不可与 Condition 配合Condition 必须与 Lock 绑定。
  3. wait/notify 必须在 synchronized 块内 ,而 await/signal 必须在 Lock 块内。

八、总结对比表(一图看懂)

text 复制代码
┌─────────────────────┬────────────────────────────┬────────────────────────────┐
│      维度           │      synchronized          │       ReentrantLock        │
├─────────────────────┼────────────────────────────┼────────────────────────────┤
│ 实现层              │ JVM (C++)                  │ JDK (Java)                 │
│ 释放方式            │ 自动                       │ 手动(必须 finally)        │
│ 公平锁              │ ❌                         │ ✅                         │
│ 可中断获取          │ ❌                         │ ✅                         │
│ 超时获取            │ ❌                         │ ✅                         │
│ 非阻塞尝试          │ ❌                         │ ✅                         │
│ 多条件变量          │ ❌ (单个 wait/notify)       │ ✅ (多个 Condition)         │
│ 状态监控            │ ❌                         │ ✅                         │
│ 性能(低竞争)      │ 相当                       │ 相当                        │
│ 性能(高竞争)      │ 相当(JVM 持续优化)        │ 略优(但差距已极小)         │
│ 代码复杂度          │ 低                         │ 中~高                      │
│ 适用场景            │ 大多数普通同步需求           │ 需要高级控制功能的复杂场景    │
└─────────────────────┴────────────────────────────┴────────────────────────────┘

九、最终建议

默认首选 synchronized ------ 简洁、安全、不易出错,满足 90% 的同步需求。

当且仅当需要以下特性时,才升级到 ReentrantLock

  • 需要超时、可中断、非阻塞的锁获取
  • 需要公平锁保证顺序
  • 需要多个条件变量实现精准线程调度
  • 需要监控锁的运行时状态

记住:简单的代码往往更可靠 。不要为了"炫技"而滥用 ReentrantLock

相关推荐
XiYang-DING2 小时前
【Java】二叉搜索树(BST)
java·开发语言·python
weixin_437957612 小时前
Mysql安装不成功
java
Lyyaoo.2 小时前
【JAVA基础面经】进程安全问题(synchronized and volatile)
java·开发语言·jvm
Andya_net2 小时前
Java | 基于 Feign 流式传输操作SFTP文件传输
java·开发语言·spring boot
_Evan_Yao2 小时前
别让“规范”困住你:前后端交互中的方法选择与认知突围
java·后端·交互·restful
星乐a3 小时前
String vs StringBuilder vs StringBuffer深度解析
java
萧逸才3 小时前
【learn-claude-code-4j】S14FeiShu - 飞书群聊智能体
java·人工智能·ai·飞书
好家伙VCC3 小时前
**发散创新:基于Rust的轻量级权限管理库设计与开源许可证实践**在现代分布式系统中,**权限控制(RBAC
java·开发语言·python·rust·开源
m0_719084114 小时前
天机学堂aaaa
java