当前位置: 首页 > news >正文

ReentrantLock 与 Synchronized 的区别

ReentrantLock 与 Synchronized 的区别

ReentrantLock 和 Synchronized 都是 Java 中用于实现线程同步的机制,但它们有显著的区别:

1. 基本性质对比

特性ReentrantLockSynchronized
实现级别JDK 层面 (java.util.concurrent.locks)JVM 层面 (关键字)
锁的获取方式显式调用 lock()/unlock()隐式获取和释放 (代码块/方法)
灵活性高 (可中断、超时、公平锁等)低 (基本功能)
性能Java 6+ 性能相当Java 6+ 性能相当
可重入性支持支持

2. 功能区别详解

(1) 锁的获取方式

// ReentrantLock - 显式锁
ReentrantLock lock = new ReentrantLock();
lock.lock();  // 必须显式获取
try {// 临界区代码
} finally {lock.unlock();  // 必须显式释放
}// Synchronized - 隐式锁
synchronized(this) {  // 自动获取和释放// 临界区代码
}

(2) 可中断性

// ReentrantLock 可响应中断
ReentrantLock lock = new ReentrantLock();
try {lock.lockInterruptibly();  // 可被中断的获取锁// 临界区代码
} catch (InterruptedException e) {// 处理中断
} finally {if(lock.isHeldByCurrentThread()) {lock.unlock();}
}// Synchronized 不可中断
synchronized(this) {// 如果其他线程不释放锁,当前线程会一直阻塞
}

(3) 公平锁支持

// ReentrantLock 可实现公平锁
ReentrantLock fairLock = new ReentrantLock(true);  // true表示公平锁// Synchronized 只能是非公平锁
synchronized(this) {  // 非公平// ...
}

(4) 尝试获取锁

// ReentrantLock 可尝试获取锁
if (lock.tryLock()) {  // 立即返回是否成功try {// 获取锁成功} finally {lock.unlock();}
}// 带超时的尝试获取
if (lock.tryLock(1, TimeUnit.SECONDS)) {  // 等待1秒try {// ...} finally {lock.unlock();}
}// Synchronized 无法实现尝试获取

(5) 条件变量 (Condition)

// ReentrantLock 支持多个条件
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();lock.lock();
try {condition.await();  // 释放锁并等待condition.signal(); // 唤醒一个等待线程
} finally {lock.unlock();
}// Synchronized 只有一个等待队列
synchronized(obj) {obj.wait();  // 等待obj.notify(); // 唤醒
}

3. 使用场景建议

使用 Synchronized 当:

  • 简单的同步需求
  • 不需要高级功能(如可中断、公平锁等)
  • 代码简洁性更重要时
  • 在 Java 5 之前的版本中

使用 ReentrantLock 当:

  • 需要可中断的锁获取
  • 需要尝试获取锁(带超时)
  • 需要公平锁机制
  • 需要多个条件变量
  • 需要更细粒度的控制

4. 性能考虑

  • 在 Java 5 中,ReentrantLock 性能优于 synchronized
  • 从 Java 6 开始,JVM 对 synchronized 进行了大量优化,两者性能相当
  • 在高度竞争的情况下,ReentrantLock 可能仍然有优势

5. 最佳实践

  1. 优先考虑 synchronized:在简单场景下,synchronized 更简洁且不易出错
  2. 需要高级功能时使用 ReentrantLock:当需要上述高级特性时选择 ReentrantLock
  3. 确保释放锁:使用 ReentrantLock 时必须在 finally 块中释放锁
  4. 避免嵌套:过度使用锁会导致死锁风险增加

总结

ReentrantLock 提供了比 synchronized 更灵活的锁操作,但随之而来的是更复杂的用法和更大的出错可能性。在大多数常见场景下,synchronized 已经足够使用且更安全。只有在确实需要 ReentrantLock 的高级功能时,才应该使用它。

ReentrantLock 与 synchronized 底层实现区别

一、synchronized 底层实现

1. JVM 层面的实现

synchronized 是 Java 语言内置的关键字,其实现完全由 JVM 负责:

  1. 监视器锁(Monitor)机制

    • 每个 Java 对象都有一个关联的监视器锁(Monitor)
    • 通过对象头中的 Mark Word 实现锁状态记录
  2. 对象内存布局

    |--------------------------------------------------------------|
    |                     Object Header (64 bits)                  |
    |------------------------------------|-------------------------|
    |        Mark Word (32/64 bits)      |  Klass Pointer (32 bits) |
    |------------------------------------|-------------------------|
    
  3. Mark Word 结构(32位 JVM):

    |-------------------------------------------------------|--------------------|
    |                  Mark Word (32 bits)                   |       State        |
    |-------------------------------------------------------|--------------------|
    |  hashcode:25 | age:4 | biased_lock:1 | lock:2 (01)     |       Normal       |
    |-------------------------------------------------------|--------------------|
    |  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 (01) |   Biased       |
    |-------------------------------------------------------|--------------------|
    |  ptr_to_lock_record:30                         | lock:2 (00) | Lightweight Lock |
    |-------------------------------------------------------|--------------------|
    |  ptr_to_heavyweight_monitor:30                  | lock:2 (10) | Heavyweight Lock |
    |-------------------------------------------------------|--------------------|
    |                                               | lock:2 (11) |    Marked     |
    |-------------------------------------------------------|--------------------|
    

2. 锁升级过程

synchronized 采用锁膨胀策略,根据竞争情况逐步升级:

  1. 无锁状态:初始状态
  2. 偏向锁(Biased Locking):
    • 适用于只有一个线程访问同步块
    • 通过 CAS 操作将线程 ID 写入 Mark Word
  3. 轻量级锁(Thin Lock):
    • 当有轻微竞争时,转换为轻量级锁
    • 使用栈中的 Lock Record 和 CAS 操作
  4. 重量级锁(Heavyweight Lock):
    • 竞争激烈时升级为重量级锁
    • 通过操作系统的互斥量(mutex)实现
    • 涉及线程阻塞和唤醒,成本较高

3. 底层指令

synchronized 同步块编译后会产生:

  • monitorenter 指令:进入同步块
  • monitorexit 指令:退出同步块(包括正常退出和异常退出)

二、ReentrantLock 底层实现

1. AQS(AbstractQueuedSynchronizer)框架

ReentrantLock 基于 Doug Lea 开发的 AQS 框架实现:

  1. 核心数据结构

    // AQS 内部类 Node 表示等待线程
    static final class Node {volatile int waitStatus;volatile Node prev;volatile Node next;volatile Thread thread;Node nextWaiter; // 用于条件队列
    }// AQS 关键字段
    private volatile int state; // 同步状态
    private transient volatile Node head; // 队首
    private transient volatile Node tail; // 队尾
    
  2. state 状态

    • 0 表示锁未被占用
    • 0 表示锁被占用,数值表示重入次数

2. 获取锁流程(非公平锁为例)

  1. tryLock() 尝试获取

    final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) { // 锁未被占用if (compareAndSetState(0, acquires)) { // CAS 尝试获取setExclusiveOwnerThread(current); // 设置当前线程为持有者return true;}}else if (current == getExclusiveOwnerThread()) { // 重入检查int nextc = c + acquires;if (nextc < 0) throw new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;
    }
    
  2. 加入等待队列

    private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) { // CAS 设置尾节点pred.next = node;return node;}}enq(node); // 自旋入队return node;
    }
    
  3. 自旋/阻塞

    final boolean acquireQueued(final Node node, int arg) {boolean interrupted = false;try {for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) { // 前驱是头节点且尝试获取锁setHead(node);p.next = null;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) // 阻塞当前线程interrupted = true;}} catch (Throwable t) {cancelAcquire(node);throw t;}
    }
    

3. 释放锁流程

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;
}

三、核心区别对比

特性synchronizedReentrantLock (基于AQS)
实现层面JVM 内置,通过对象头和 monitor 实现JDK 实现,基于 CAS 和 CLH 队列
锁状态存储对象头中的 Mark WordAQS 中的 state 变量
等待队列JVM 维护的等待队列显式的 CLH 队列(双向链表)
竞争处理锁膨胀(偏向锁→轻量级锁→重量级锁)直接 CAS 竞争,失败后入队
阻塞机制重量级锁使用 OS 互斥量使用 LockSupport.park() 阻塞
可重入实现通过_recursions计数器通过 state 计数器
性能优化偏向锁、适应性自旋等 JVM 优化纯 Java 实现,依赖 CAS 和 park/unpark
中断响应不支持支持 lockInterruptibly()
公平性只有非公平模式可选择公平/非公平模式
条件变量只有一个 wait/notify 队列可创建多个 Condition 对象

四、底层原理图示

synchronized 锁状态转换

[无锁] → [偏向锁] → [轻量级锁] → [重量级锁]↑____________|          |撤销偏向锁      自旋失败

ReentrantLock 获取锁流程

尝试获取锁 (CAS)↓
成功? → 执行同步代码↓ No
加入CLH队列↓
自旋检查前驱节点↓
前驱是头节点? → 尝试获取锁 → 成功? → 成为新头节点↓ No                           ↓ No
阻塞等待唤醒                  继续自旋

五、选择建议

  1. 优先 synchronized

    • 简单同步场景
    • 不需要高级功能
    • Java 6+ 性能已优化
  2. 选择 ReentrantLock

    • 需要可中断、超时、公平锁等高级特性
    • 需要多个条件变量
    • 需要更细粒度的控制
    • 明确知道锁竞争情况的高并发场景

理解这些底层实现差异有助于在特定场景下做出更合适的技术选型,并能够更好地进行性能调优和问题排查。

http://www.dtcms.com/a/272656.html

相关文章:

  • 给MySQL做定时备份,一天3次
  • method_name字段是什么
  • 单片机基础(STM32-DAY2(GPIO))
  • Linux驱动06 --- UDP
  • 飞书AI技术体系
  • web 系统对接飞书三方登录完整步骤实战使用示例
  • 低温冷启动 高温热启动
  • OpenCV 图像进阶处理:特征提取与车牌识别深度解析
  • 醋酸镨:闪亮的稀土宝藏,掀开科技应用新篇章
  • Spring IoC 如何注入一些简单的值(比如配置文件里的字符串、数字)?
  • 【文献阅读】Depth Anything: Unleashing the Power of Large-Scale Unlabeled Data
  • MyBatis 使用教程及插件开发
  • 自动驾驶环境感知:天气数据采集与融合技术实战
  • AI-Sphere-Butler项目语音切换数字人管家形象功能老是开发不成功。
  • Oracle 数据库管理与维护实战指南(用户权限、备份恢复、性能调优)
  • 深度学习与图像处理案例 │ 基于深度学习的自动驾驶小车
  • GitHub上优秀的开源播放器项目介绍及优劣对比
  • 申请注册苹果iOS企业级开发者证书需要公司拥有什么规模条件
  • Nacos的基本功能以及使用Feign进行微服务间的通信
  • 【网络编程】 TCP 协议栈的知识汇总
  • ZW3D 二次开发-创建圆柱体
  • Qt cannot find C:\WINDOWS\TEMP\cctVBBgu: Invalid argument
  • QT5使用cmakelists引入Qt5Xlsx库并使用
  • 达梦数据库不兼容 SQL_NO_CACHE 报错解决方案
  • C++交叉编译工具链制作以及QT交叉编译环境配置
  • 生产环境CI/CD流水线构建与优化实践指南
  • 医院多部门协同构建知识库-指南库-预测模型三维网络路径研究
  • 12大产品规划工具对比:功能、价格与适用场景
  • (LeetCode 面试经典 150 题 ) 11. 盛最多水的容器 (贪心+双指针)
  • 2023 年 12 月青少年软编等考 C 语言七级真题解析