详细剖析乐观锁和悲观锁

乐观锁与悲观锁,本质上是一种思想,体现了从不同角度看待线程同步。在Java和数据库中都有此概念对应的实际应用。

一、悲观锁

悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(例如:Java的synchronized关键字),也可以是对数据加锁(如 MySQL 数据库的排它锁)

Java 5之前的版本,关键字 synchronized 是悲观锁 

Java 5+的 Lock 接口的实现类是悲观锁,线程一旦得到锁,其他需要锁的线程就挂起的情况就是悲观锁。

让没有得到锁资源的线程进入阻塞状态,而后在争夺到锁资源后恢复为运行状态,这个过程中涉及到操作系统用户模式和内核模式的互相转换,也叫上下文切换,代价比较高。

二、乐观锁

乐观锁的实现方式主要有两种:CAS机制和版本号机制

1. 轻量级锁 volatile(保证了可见性 + JMM 禁止指令重拍)。

2. 是通过使用无锁编程来实现,最常采用的是CAS算法。

volatile保证了变量的内存可见性。会确保对被声明为 volatile变量的写操作对其他线程立即可见。让每个线程看到的是最新的值,从而避免了多线程环境中的数据不一致问题。 禁止指令重排序:volatile 还可以防止指令重排序优化。在多线程环境下,编译器可能会进行指令重排序以提高性能,但这种重排序可能会破坏程序的预期行为。通过使用 volatile,可以确保指令按顺序执行。 防止编译器优化:volatile 告诉编译器该变量可能会被意外地修改,因此需要使用内存屏障来确保变量的读写顺序符合程序的要求。

Java atomic类的递增操作就通过CAS自旋实现的。假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。


CAS机制使用了3个基本操作数:内存地址V,预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。如果不同,说明有别的线程进行了操作,则要更新预期值,循环执行直到成功(自旋)。


CAS的缺点:


     1.CPU开销

较大。在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
     2.不能保证代码块的原子性。CAS机制所保证的只是一个变量的原子性操作,只能实现变量同步,要保证整个代码块的同步还是需要使用Synchronized。

三、适用场景

悲观锁:适合写操作多的场景,先加锁可以保证写操作时数据正确。
乐观锁:适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

四、区别

悲观锁阻塞事务;

乐观锁回滚重试。

总结:两者各有优缺点,不能认为一种一定好于另一种,要看具体使用场景来决定。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值