乐观锁与悲观锁

悲观锁


每次修改时,都会认为别人会修改,所以在拿数据时加锁。别人拿数据就会阻塞到它拿到锁。

传统的关系型数据库就使用了这种悲观锁,行锁、表锁,读锁、写锁,都是在操作之前先上锁。Java的Synchronized和Reetrantlock都是这种独占锁。


乐观锁


每次修改时都认为别人不会在这期间修改数据,所以不会上锁。但是更新的时候,会判断一下在此期间数据有没有其它线程修改。

乐观锁的实现方式有版本号和CAS算法。


对比


悲观锁在多写场景比较合适。
乐观锁在多读的场景比较适合。
当写操作比较多的时候,乐观锁会不断的retry,反而降低了性能,所以不如使用悲观锁。


乐观锁的两种实现方式


  1. 版本号
    在数据表加上一个version字段,表示字段被修改的次数。当字段被修改一次就会加一。当线程获取数据时也会获取version,当修改数据时,只有当读取到的version值为当前数据库的值相同时才会修改,否则会不断的retry。

  2. CAS算法
    Compare And Swap,CAS涉及三个操作数:

    • 需要读写的内存值 V
    • 进行比较的值 A
    • 拟写入的新值 B

    当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。


CAS算法的缺点


1. ABA问题

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。

JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2. 循环开销大

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。


CAS和Syn

1.CAS适合线程冲突不那么严重的情况,在线程冲突轻的时候,CAS无锁不需要切换线程,没有上下文的切换,因此有更高的性能。
2.Syn适合线程冲突严重的情况,在线程竞争激烈的情况,CAS可能会一直循环,资源浪费严重,反而不如Syn。

Syn在1.6时进行了优化,引入了偏向锁和轻量级锁。Synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值