94- 悲观锁、乐观锁、CAS

一、悲观锁

悲观锁是一种并发控制机制,它假设在多线程环境下会有并发冲突的情况发生,因此采取悲观的态度,在访问共享资源之前先将其锁住,以防止其他线程对其进行修改。悲观锁的核心思想是“先锁定再操作”。

在悲观锁中,当一个线程要访问共享资源时,它会先获取锁,其他线程需要等待该线程释放锁才能继续执行。当一个线程持有锁时,其他线程无法访问被锁定的资源,这样就保证了数据的一致性和完整性。

常见的悲观锁实现方式包括:

  1. synchronized 关键字:使用 synchronized 关键字可以对代码块或方法进行加锁,使得在同一时间只能有一个线程进入被锁定的代码块或方法。

  2. ReentrantLock 类:ReentrantLock 是 Java.util.concurrent 包中提供的可重入锁实现,通过 acquire() 方法获取锁,并在 finally 块中使用 release() 方法释放锁。

悲观锁的优点是确保数据的安全性,它适用于写操作较多的场景,能够有效避免数据竞争和并发冲突。然而,悲观锁的缺点是由于加锁操作需要消耗额外的时间和资源,当并发程度较高时,可能会导致性能下降

在实际应用中,使用悲观锁需要谨慎权衡,特别是在高并发场景下,可以考虑使用乐观锁等其他并发控制机制来提高性能。

二、乐观锁

乐观锁是一种并发控制机制,与悲观锁相反,它假设在多线程环境下并发冲突的情况很少发生,因此采取乐观的态度,在访问共享资源时不加锁,而是在更新时检查是否有其他线程同时修改了该数据。乐观锁的核心思想是“先操作再检查”。

在乐观锁中,每个线程都可以读取和修改共享资源,但在更新资源时需要进行额外的检查来确保数据的一致性。通常,乐观锁会引入一个版本号或时间戳来追踪和判断资源的变化情况。

常见的乐观锁实现方式包括:

  1. 版本号机制:在数据结构中引入一个版本号字段,每次更新资源时,将版本号加一。当一个线程要更新资源时,先读取当前版本号,然后执行更新操作,并将新的版本号写回。如果在更新过程中检测到版本号已经发生变化(即其他线程已经修改了数据),则需要进行冲突处理,例如重试或回滚操作。

  2. 时间戳机制:类似于版本号机制,不同之处在于使用时间戳来判断数据是否被修改。每次更新资源时,将时间戳记录下来,并在更新时比较当前时间戳与读取时的时间戳,如果不一致则表示数据已被修改。

乐观锁的优点是不需要加锁,减少了线程之间的竞争和阻塞,能够提高系统的并发性能。然而,乐观锁需要额外的逻辑来处理冲突,并且在并发程度较高时可能会有较多的重试操作。

在实际应用中,选择悲观锁还是乐观锁取决于具体的业务场景和并发需求。乐观锁适用于读操作较多、冲突发生的概率较低的场景,能够提高系统的并发性能。

三、CAS

CAS(Compare and Swap,比较并交换)是一种原子操作,常用于并发编程中的乐观锁机制。它允许在多线程环境下实现对共享数据的安全访问和更新。

CAS 操作的基本思想是,先比较内存位置的当前值与期望值是否相等,如果相等,则将新值写入内存位置;如果不相等,则说明其他线程已经修改了内存位置的值,操作失败,需要进行重试或采取其他处理措施。

CAS 操作包括三个操作数:

  1. 内存位置:需要进行读写操作的共享数据的内存地址。
  2. 期望值:期望的内存位置的值,在执行 CAS 操作之前进行比较。
  3. 新值:要写入到内存位置的新值,当且仅当内存位置的值等于期望值时,才会写入新值。

CAS 操作的原子性是通过硬件级别的原子指令实现的,保证了操作的不可分割性。因此,CAS 可以在多线程环境下保证共享数据的一致性。

CAS 操作的优点如下:

  1. 高效性:由于不需要加锁和解锁等额外开销,CAS 操作在高并发情况下的性能通常优于使用传统锁机制。
  2. 无阻塞性:CAS 操作不会阻塞线程,如果操作失败,可以立即进行其他处理而不需要等待。
  3. 无锁性:CAS 操作不需要使用显式锁,避免了锁的竞争和死锁问题。

在 CAS 操作中,如果其他线程在期望值被修改之后并最终又恢复为原来的值,CAS 无法感知到这种变化。为了解决这个问题,可以引入版本号、时间戳等机制来增加额外的判断条件。

总结来说,CAS 是一种乐观锁机制,通过比较内存位置的当前值与期望值来实现对共享数据的安全访问和更新。它的优势在于高效性、无阻塞性和无锁性。在并发编程中,CAS 是一种重要的并发控制手段。

### 乐观锁悲观锁CAS实现机制 #### 悲观锁 悲观锁的核心理念在于认为并发环境下的资源访问一定会发生冲突,因此在操作共享资源之前会先对其进行锁定。这种锁通常由数据库或者程序中的显式锁来实现[^2]。例如,在SQL语句中可以通过`SELECT ... FOR UPDATE`的方式对记录加锁,从而防止其他事务修改该记录[^5]。 ```sql -- 使用悲观锁的 SQL 示例 SELECT status FROM t_goods WHERE id=1 FOR UPDATE; ``` 悲观锁的主要特点是它能够完全阻止其他线程或进程在同一时间访问相同的资源,适用于写密集型场景,但在高并发读取的情况下可能会带来性能瓶颈。 --- #### 乐观锁 乐观锁则基于一种假设:大多数情况下不会存在并发冲突,所以在执行更新前不需要加锁。当检测到冲突时,则采取重试策略直至成功[^3]。其主要实现方式有两种: - **版本号机制** 数据表中增加一个字段用于表示当前数据的状态(通常是整数类型的版本号)。每次更新时都会验证此版本号是否匹配最新状态;如果不匹配说明有其他事务已经对该条目进行了更改,此时可以抛出异常或者重新尝试提交新请求[^4]。 - **CAS (Compare And Swap)** CAS 是一种无锁算法,通过比较内存位置上的旧值与预期值是否相同来进行原子化交换操作。只有当两者相等时才会替换为目标值并返回 true 表明此次变更有效;否则仅反馈 false 而不做任何改动[^1]。 以下是利用 `java.util.concurrent.atomic.AtomicInteger` 类演示如何运用 CAS 方法的一个简单例子: ```java import java.util.concurrent.atomic.AtomicInteger; public class CasExample { private static AtomicInteger atomicInt = new AtomicInteger(0); public void increment() { int expectValue, newValue; do { expectValue = atomicInt.get(); // 获取当前值 newValue = expectValue + 1; // 计算新的期望值 } while (!atomicInt.compareAndSet(expectValue, newValue)); // 尝试设置新值 } } ``` 上述代码片段展示了如何借助于 compare-and-swap 来保证计数值的安全递增过程即使面对多个线程竞争也依然保持一致性。 --- #### 区别对比 | 特性 | 悲观锁 | 乐观锁 | |--------------------|---------------------------------|--------------------------------| | 基本思想 | 预期会有冲突 | 预期不会有冲突 | | 锁定时机 | 提前锁定 | 更新时刻才判断 | | 并发处理能力 | 较低 | 更高效 | | 性能影响 | 可能导致死锁等问题 | 失败需重试 | | 应用场景 | 写频繁的数据集 | 主要是读多写的业务逻辑 | 尽管这两种锁各有优劣,但实际应用过程中往往需要根据具体需求权衡选择合适的方案。对于那些经常面临大量随机存取压力的应用来说,采用乐观控制可能更为合适一些;而对于某些特定领域比如银行转账之类涉及金钱转移的操作而言,由于安全性至关重要所以更倾向于依赖传统的互斥手段即悲观封锁形式加以保护。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值