ReentrantLock的公平锁和非公平锁实现图解

本文深入解析公平锁和非公平锁的工作原理,重点阐述两者的获取与释放过程,以及可重入特性。对比公平锁与非公平锁在资源获取上的差异,探讨其在并发控制中的应用。

公平锁的获取和释放:

在这里插入图片描述
可以看到这个过程跟AQS独占锁的获取和释放是一样的,实现公平锁的关键是tryRelease方法的实现:
在这里插入图片描述

公平锁实现关键:

当有资源时也会判断当前有没有线程在等待,只要有线程在等待不管有没有资源都要排队等待,这就保证了线程完全是按先后顺序执行。

可重入实现细节:

同一个线程可以在没有资源情况下无限次获取锁,通过上图可以看到,在state不是0的情况下会判断当前线程是否是持锁线程,是的情况下会改变state的值并return true。此时要注意,同一个线程lock几次,也要unlock几次,不然会死锁。

非公平锁的获取和释放:

在这里插入图片描述

非公平锁有两次非公平的方式:

1.在刚调lock时会直接CAS尝试获取锁。
2.在nonfairTryAcquire方法中发现有资源的情况下会直接CAS尝试获取锁,而不会管等待队列中有没有其他线程在排队。

公平锁和非公平锁实现细节差异:

公平锁在有资源的情况下也会判断等待队列中是否有线程在等待,而非公平锁会有两次非公平获锁的机会,但如果失去这两次机会则也要乖乖的排队等候按时间顺序执行。

<think> 我们正在讨论Java中的悲观乐观,需要回答用户的问题:Java中乐观悲观的种类及区别。 根据引用资料,我们可以总结如下: 1. 悲观: - 定义:悲观认为自己在使用数据时一定会有其他线程来修改数据,因此在获取数据时会先加,确保数据不会被其他线程修改。 - 实现方式:在Java中,synchronized关键字Lock的实现类(如ReentrantLock)都是悲观的典型代表。 - 使用场景:适合写操作多的场景,先加可以保证写操作时数据正确。 - 调用方式: - 使用synchronized修饰方法(如引用[4]中的m1方法) - 使用Lock接口的实现类(如ReentrantLock)进行显式加(如引用[4]中的m2方法) 2. 乐观: - 定义:乐观认为对共享资源的访问没有冲突,所以不需要加。它采用一种基于冲突检测的机制(通常使用版本号或CAS操作)来保证线程安全。 - 实现方式: - CAS(Compare and Swap)操作:Java中的原子类(如AtomicInteger)就是通过CAS实现的乐观。 - 版本号机制:在数据表中增加一个版本号字段,更新数据时比较版本号。 - 使用场景:适合读操作多的场景,不加使得读操作的性能大幅提升。 3. 两者的区别: - 加与否:悲观在操作数据之前会上;乐观则不会上,而是在更新时检查是否有其他线程修改了数据。 - 适用场景:悲观适合写操作多的场景;乐观适合读操作多的场景。 - 性能开销:悲观在竞争激烈时会导致其他线程阻塞,性能开销大;乐观在竞争不激烈时性能较好,但在竞争激烈时,CAS自旋会带来较大的CPU开销。 - 实现机制:悲观依靠底层同步机制(如synchronized或Lock);乐观则使用CAS或版本号。 4. 种类: - 悲观的种类: - synchronized(可重入) - Lock接口的实现类(如ReentrantLock,ReentrantReadWriteLock等) - 乐观的种类: - CAS算法:由Unsafe类提供支持,原子类(AtomicInteger, AtomicLong, AtomicReference等)使用CAS。 - 版本号机制:通常用于数据库,但在Java中可以通过在对象中增加版本字段,并在更新时检查来实现。 5. 引用资料中的关键点: - 引用[1]:乐观使用版本号或CAS操作来实现。 - 引用[2]:图解说明了悲观乐观实现使用场景。 - 引用[3]:乐观又称为“无”,使用CAS技术。 - 引用[4]:悲观实现方式(synchronizedLock),适合写操作多的场景;乐观实现方式(CAS版本号)。 - 引用[5]:悲观在高并发场景下可能带来性能问题,此时可以使用乐观。 根据以上内容,我们可以组织答案。 </think> ### Java 中悲观与乐观的种类及区别 悲观乐观是两种不同的并发控制策略,其核心差异在于对**数据冲突概率的预判****机制实现方式**。下面从种类、原理、实现及适用场景进行详细对比: --- #### 一、**悲观(Pessimistic Locking)** **核心思想**:假设数据冲突必然发生,访问数据前强制加(独占资源)。 **种类及实现**: 1. **`synchronized` 关键字** - 修饰方法或代码块,自动获取/释放 - 示例: ```java public synchronized void updateBalance() { // 修改共享资源 } ``` 2. **`Lock` 接口实现类(如 `ReentrantLock`)** - 需显式加,支持公平、可中断等特性 - 示例: ```java ReentrantLock lock = new ReentrantLock(); public void transfer() { lock.lock(); // 显式加 try { // 操作共享资源 } finally { lock.unlock(); // 必须手动释放 } } ``` **特点**: - **强一致性**:读写操作实时同步,保证数据绝对安全[^4] - **开销大**:频繁加/解增加线程切换成本,高并发时易阻塞[^5] - **适用场景**:写密集型操作(如金融交易、库存扣减)[^4] --- #### 二、**乐观(Optimistic Locking)** **核心思想**:假设数据冲突极少发生,操作时**不加**,提交更新前校验数据是否被修改。 **种类及实现**: 1. **CAS(Compare and Swap)机制** - 通过 CPU 原子指令(如 `cmpxchg`)实现 - Java 封装类:`AtomicInteger`、`AtomicReference` 等 - 示例: ```java AtomicInteger balance = new AtomicInteger(100); balance.compareAndSet(100, 90); // 仅当原值=100时更新为90 ``` 2. **版本号机制(Versioning)** - 数据表增加版本号字段,更新时校验版本 - 示例(数据库伪代码): ```sql UPDATE accounts SET balance=90, version=2 WHERE id=1 AND version=1; // 版本匹配才更新 ``` **特点**: - **无读操作**:读性能极高,无线程阻塞[^3] - **冲突检测**:通过版本号或 CAS 值校验数据一致性[^1] - **适用场景**:读多写少场景(如缓存、配置管理)[^2] --- #### 三、核心区别总结 | **维度** | **悲观** | **乐观** | |------------------|-------------------------------------|-------------------------------------| | **冲突预判** | 假设冲突必然发生 | 假设冲突极少发生 | | **加时机** | 操作前强制加 | 操作不加,提交时校验 | | **实现方式** | `synchronized`、`ReentrantLock` | CAS(原子类)、版本号机制 | | **一致性** | 强一致性(实时同步) | 最终一致性(提交时校验) | | **性能开销** | 高(竞争、线程阻塞) | 低(无读,CAS 自旋开销可控) | | **适用场景** | 写密集型(如转账、库存扣减)[^4] | 读密集型(如缓存、事件监听)[^2] | | **典型异常** | 死(需避免嵌套) | 更新失败(需重试逻辑) | > **数学描述冲突概率的影响**: > 设 $P_c$ 为数据冲突概率,$C_p$ 为悲观开销,$C_o$ 为乐观开销。 > - 当 $P_c > \frac{C_o}{C_p}$ 时,悲观更优(高冲突场景) > - 当 $P_c < \frac{C_o}{C_p}$ 时,乐观更优(低冲突场景)[^5] --- #### 四、选型建议 1. **悲观适用场景**: - 强一致性要求(如银行交易) - 写操作占比 > 50% - 临界区代码复杂(避免重试逻辑)[^4] 2. **乐观适用场景**: - 读操作占比 > 80% - 冲突概率低(如配置更新) - 需高吞吐量(如秒杀库存查询)[^3][^5] > **案例对比**: > - 悲观:用户余额修改(`synchronized` 保证转账原子性)[^4] > - 乐观:商品库存查询(`AtomicInteger` 无读取实时库存)[^2] ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值