ReenTrantLock和Synchronized锁

ReenTrantLock与Synchronized锁对比解析

1. synchronized 锁的机制和升级过程

偏向锁(Biased Locking)

  • 原理:当一个线程第一次请求锁时,JVM 会记录该线程的 ID,并将锁标记为偏向锁。此后,若同一线程再次访问该锁,JVM 会直接认为该线程已经持有锁,从而避免加锁和解锁操作,提升性能。

  • 工作流程

    1. 线程第一次请求锁时,JVM 将锁对象标记为偏向锁,并记录线程 ID。

    2. 当相同线程再次访问该锁时,JVM 检查对象头中的线程 ID,若匹配,线程直接获取锁,无需再次加锁。

    3. 如果其他线程争用锁,偏向锁会被撤销,锁将进入轻量级锁或重量级锁状态。

  • 适用场景:适用于没有竞争的单线程场景,可以显著提升性能。

轻量级锁(Lightweight Locking)

  • 原理:轻量级锁通常是偏向锁升级后的结果。当多个线程争用同一个锁时,偏向锁无法继续使用,JVM 会将其升级为轻量级锁。此时,线程会通过自旋(CAS)来尝试获取锁,而非立即阻塞。

  • 工作流程

    1. 线程请求锁时,如果当前为偏向锁且无竞争,线程直接获得锁。

    2. 如果发生竞争,偏向锁升级为轻量级锁,线程通过自旋(CAS)尝试获取锁。

    3. 如果自旋次数过多,锁将升级为重量级锁,导致线程阻塞。

  • 适用场景:适用于锁竞争较少的场景,减少了上下文切换的开销。

自旋锁(Spin Lock)

  • 原理:线程在尝试获取锁时,如果锁被占用,线程不会立即阻塞,而是会持续尝试获取锁,这就是“自旋”。自旋锁减少了线程上下文切换的开销。

  • 工作方式

    • 线程使用 CAS 获取锁,如果获取成功,则持有锁;

    • 如果锁被占用,线程会不断自旋(重试),直到获取锁或者达到自旋次数上限。

    • 自旋锁是轻量级的,但如果自旋过多,可能浪费 CPU 时间,因此当自旋超过一定次数时,锁会升级为重量级锁。

  • 适用场景:适用于竞争不激烈的场景,减少了系统资源的消耗。

重量级锁(Heavyweight Locking)

  • 原理:当自旋锁尝试多次失败,或线程竞争过于激烈时,JVM 会将轻量级锁升级为重量级锁。此时,线程将被阻塞,操作系统会介入进行调度。

  • 适用场景:适用于锁竞争非常激烈的场景。

锁升级过程总结

  • 偏向锁:无竞争时,JVM 会优先使用偏向锁,避免不必要的加锁。

  • 轻量级锁:有线程竞争时,偏向锁被升级为轻量级锁,线程通过自旋(CAS)争抢锁。

  • 重量级锁:当自旋失败或竞争非常激烈时,轻量级锁升级为重量级锁,线程会被阻塞,等待操作系统调度。

你发的图片内容总结了 synchronized 的实现原理,其核心观点是准确的。下面我根据这张图进行提炼和扩展说明,让你对 synchronized 的底层机制有更完整的理解:


🧩 synchronized 是如何实现线程同步的()

一、作用与基本概念

  • synchronized 是 Java 提供的原生同步机制,用于控制多个线程对共享资源的访问

  • 它可以修饰:

    • 实例方法(锁的是当前对象 this

    • 静态方法(锁的是 Class 对象)

    • 代码块(锁的是指定的对象)

✅ 关键点:任意时刻只有一个线程能获取锁,进入同步代码


二、底层原理

🔒 基于 监视器锁(Monitor Lock) 实现
  • 每个 Java 对象都隐含地与一个 Monitor(监视器锁) 关联

  • 当一个线程执行 synchronized 时,会去尝试获取这个对象的 Monitor

  • 获取成功才可以进入同步块,否则会阻塞等待

🧠 依赖于操作系统的 互斥锁(Mutex) 实现
  • Monitor 在 HotSpot VM 中由 ObjectMonitor 实现

  • ObjectMonitor 内部可能调用操作系统的原生锁机制(如互斥量)


三、对象头 + Mark Word(扩展说明)

图中提到:

Java 的对象有一个头(Object Header),其中包含一个 Mark Word,用于存储锁的信息。

这是实现 synchronized 的关键点之一:

📦 Mark Word 中的信息包括:
  • 锁标志位(无锁、偏向锁、轻量级、重量级)

  • 锁拥有者线程 ID(在加锁状态下)

  • 哈希码、GC 分代信息(非加锁时)

🔄 锁状态会自动升级

根据竞争情况,JVM 会进行锁的升级或降级以优化性能:

无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 偏向锁:单线程反复加锁时极高效(无 CAS)

  • 轻量级锁:多线程但无竞争,采用 CAS 操作

  • 重量级锁:多线程并发竞争严重,线程阻塞


✅ 小结:

内容类别说明
功能目的synchronized 保证共享资源在并发访问中不被破坏
作用范围方法、代码块
底层实现基于对象的 Monitor(监视器锁)
操作系统基础Monitor 内部可能调用 native 的 Mutex
对象结构对象头(含 Mark Word),保存锁信息
性能优化锁状态根据竞争情况升级:偏向 → 轻量 → 重量

synchronized 锁的限制

  • 作用范围synchronized 锁是 JVM 层的锁,作用范围仅限于单个 JVM 实例中的线程间同步。

  • 高并发下的失效synchronized 锁无法跨 JVM 实例(跨服务)进行同步,因此在分布式系统中,synchronized 不能用于跨服务锁定,且无法保证在高并发下的有效性。

  • synchronized锁字符串:只能锁住常量池中的字符串,不能锁住new出来的字符串,因为每次new的字符串都是新对象,新资源。


2. ReentrantLock 锁机制

ReentrantLock 的特点

  • 可重入性ReentrantLock 是可重入锁,即同一个线程可以多次获取同一个锁而不会导致死锁。

  • 手动加锁和解锁ReentrantLock 需要通过显式调用 lock() 方法加锁,通过 unlock() 方法释放锁,开发者需要注意在 finally 块中释放锁以防止死锁。

  • 公平性选择ReentrantLock 提供了公平锁和非公平锁两种模式。公平锁保证按请求顺序获取锁,非公平锁则允许线程“插队”,通常非公平锁性能更高。

  • 性能ReentrantLock 在高并发情况下表现优越,能够支持更多的控制操作,如锁中断、定时锁等。


✅ ReentrantLock 的 lock()tryLock() 方法区别笔记


一、两者的基本定义

方法作用特点
lock()获取锁,如果锁被其他线程持有,则阻塞等待直到获得锁不可中断(可配合 lockInterruptibly() 支持中断)
tryLock()尝试获取锁,立即返回获取结果,不等待非阻塞,立即得知是否成功

二、方法签名对比

// lock(): 阻塞式获取锁
void lock()

// tryLock(): 非阻塞尝试获取锁
boolean tryLock()

// tryLock(long timeout, TimeUnit unit): 限时尝试获取锁
boolean tryLock(long timeout, TimeUnit unit)
    throws InterruptedException

三、核心区别对比

比较项lock()tryLock()
是否阻塞是,若获取不到锁会阻塞否,获取不到立即返回
返回类型void(阻塞直到成功)boolean(立即返回是否成功)
可否响应中断否(使用 lockInterruptibly() 可中断)限时版本可响应中断
使用场景更适用于必须拿到锁的关键逻辑更适用于尝试执行、失败也可以继续其他逻辑的场景
超时机制不支持超时支持超时(重载版本)
死锁容忍性容易阻塞造成死锁可避免死锁(配合超时或失败处理逻辑)

四、使用示例

1. lock() 示例

lock.lock();
try {
    // 临界区逻辑
} finally {
    lock.unlock();
}

2. tryLock() 示例(立即返回)

if (lock.tryLock()) {
    try {
        // 拿到锁的逻辑
    } finally {
        lock.unlock();
    }
} else {
    // 获取失败的逻辑,如日志、降级
}

3. tryLock(long, TimeUnit) 示例(限时等待)

if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
    try {
        // 成功获取锁
    } finally {
        lock.unlock();
    }
} else {
    // 超时未获取锁,处理降级逻辑
}

五、适用场景对比

场景推荐方法原因
非常关键代码,必须持有锁lock()阻塞直到获取锁,确保逻辑执行
避免死锁 / 降级处理tryLock()获取不到锁可以立即返回或降级处理
高并发非强一致性任务tryLock()失败快速返回,避免线程长时间等待
可响应中断的任务调度lockInterruptibly()tryLock(timeout)能中断等待,增强灵活性

六、最佳实践建议

  • 不要忘记在 finally 中释放锁(防止死锁):

    lock.lock();
    try {
        // ...
    } finally {
        lock.unlock();
    }
    
  • 使用 tryLock() 做降级方案设计,提高系统容错能力;

  • lock() 可配合 lockInterruptibly() 使用,以便支持中断响应;

  • 在高并发系统中,使用 tryLock(timeout) 可防止长时间阻塞导致线程资源耗尽。


✅ 总结表

方法阻塞可中断是否返回布尔值支持超时使用建议
lock()保守获取锁,关键逻辑
tryLock()快速尝试、非核心逻辑
tryLock(timeout)限时阻塞限时降级、避免死锁
lockInterruptibly()支持线程中断的阻塞锁

ReentrantLocksynchronized 的区别

  • 类型

    • synchronized 是 Java 语言的内置关键字。

    • ReentrantLock 是一个实现了 Lock 接口的类。

  • 加锁和释放锁

    • synchronized:由 JVM 自动管理加锁与释放锁。

    • ReentrantLock:由程序员显式调用 lock()unlock() 来管理。

  • 锁的公平性

    • synchronized:默认是非公平锁。(升级不了公平锁)

    • ReentrantLock:可以选择公平锁(通过构造函数参数)或非公平锁。

  • 死锁检测

    • synchronized:无法主动检测死锁。

    • ReentrantLock:可以通过 tryLock()lockInterruptibly() 方法来避免死锁。

  • 底层实现

    • synchronized:JVM 层面的锁,底层通过不同的锁机制(如偏向锁、轻量级锁、重量级锁)进行升级。

    • ReentrantLock:基于 AQS(AbstractQueuedSynchronizer)实现,支持更多灵活的控制。

性能比较

  • synchronized:虽然 JVM 在底层优化了锁的升级过程,但由于其无法灵活控制锁行为,性能在高并发情况下不如 ReentrantLock

  • ReentrantLock:提供了更多的灵活性和更好的性能,特别是在高并发和需要更精细锁控制的场景。


3. 锁的跨服务同步问题

  • synchronized 锁是 JVM 层面的锁,它无法跨 JVM 实例同步。因此,在 分布式系统 中,synchronized 锁无法用于跨服务的线程同步。

  • 在分布式系统中,需要使用 分布式锁 来同步不同 JVM 实例中的线程。常见的分布式锁实现有 Redis 分布式锁、Zookeeper 分布式锁等。


总结对比表:synchronizedReentrantLock

特性synchronizedReentrantLock
锁类型JVM 层的关键字类,Lock 接口实现
加锁和释放锁自动加锁与释放锁手动加锁和释放锁(lock()unlock()
锁的公平性默认非公平锁可以选择公平锁或非公平锁
死锁检测无法主动检测死锁支持通过 tryLock()lockInterruptibly() 检测
锁的信息存储对象头中state 字段
性能高并发下可能性能较差性能较优,特别在高并发情况下
锁的粒度锁住方法或代码块提供更多的灵活性和细粒度的锁控制
支持可重入性支持支持
默认锁类型非公平锁默认非公平锁
跨服务锁支持仅在同一 JVM 内有效,不能跨服务同步不能跨服务锁,需使用分布式锁

总结

  • synchronized 是 JVM 层面的锁,适合单 JVM 环境中的线程同步,但无法跨 JVM 实例(跨服务)同步,且在高并发场景下性能较差。

  • ReentrantLock 提供了更多的灵活性和控制,支持手动加锁、选择公平锁与非公平锁、死锁检测等功能,适用于更复杂的并发场景,并且能够在高并发下提供较好的性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值