详解 —— 常见锁策略及ABA问题

目录

一、常见锁策略

1、悲观锁VS乐观锁

2、读写锁VS普通互斥锁

3、重量级锁VS轻量级锁

4.挂起等待锁VS自旋锁

5、公平锁VS非公平锁 

 6、可重入锁VS不可重入锁

小结 

二、CAS

1、CAS的作用

2、ABA问题 

三、synchronized的锁优化机制

1.锁膨胀/锁升级

2、锁粗化 

3、锁消除 


一、常见锁策略

一般来说,锁策略和普通的程序员基本没啥关系,跟实现锁的人有紧密的联系。这里提到的锁策略也不是局限于Java这门语言的,是适用于所有跟锁相关的情况的。

1、悲观锁VS乐观锁

悲观锁:预期锁冲突的概率很高

乐观锁:预期锁冲突的概率很低

悲观锁需要做的事情更多,那么效率也越低。乐观锁则刚好相反,做的事更少,效率更高

2、读写锁VS普通互斥锁

普通互斥锁:只有两个操作,加锁和解锁;只要两个线程针对同一个对象加锁,就会产生互斥。

读写锁:分成了三个操作:加读锁,加写锁,解锁;其中读锁和读锁之间是不会产生互斥关系的,而读锁和写锁以及写锁和写锁之间才会产生互斥关系。

3、重量级锁VS轻量级锁

重量级锁和轻量级锁的概念跟悲观乐观锁是有一定的重叠的,我们可以这样认为:乐观悲观锁是看待问题的态度,而这个态度决定的结果是重量级和轻量级锁。

重量级锁就是做了更多的东西,开销更大

轻量级锁做的事更少,开销更少

一般悲观锁都是重量级锁,乐观锁一般都是轻量级锁(当然这也不是绝对的)

在使用的锁当中,如果该锁是基于内核的一些功能来实现的,此时一般认为该锁是重量级锁;如果该锁是纯用户态(用户态代码更可控,也更高效)实现的,一般认为这把锁是轻量级锁。 

4.挂起等待锁VS自旋锁

挂起等待锁往往是通过内核的一些机制来实现的,一般都比较重(重量级锁的典型实现);而自旋锁往往是通过用户态代码来实现的,一般较轻(轻量级锁的经典实现)。

5、公平锁VS非公平锁 

公平锁:遵循先来后到的原则

非公平锁:不讲究先来后到,不管是不是先来的,获取这把锁的概率是相同的

 6、可重入锁VS不可重入锁

同一个线程针对同一把锁连续加锁两次,如果不会造成死锁的就是可重入锁;反之就是不可重入锁。

小结 

 以synchronized来举例:

1.既是一个乐观锁也是一个悲观锁(根据锁冲突的激烈程度自适应)

2.不是读写锁,只是一个普通的互斥锁

3.既是重量级锁也是轻量级锁(同1)

4.轻量级的部分基于自旋锁,重量级部分基于挂起等待锁

5.是非公平锁

6.是可重入锁

二、CAS

CAS是compre and swap的缩写,我们假设内从中的原数据V,旧的预期值A,需要修改的值B:

1.比较A与V是否相同

2.如果相同则将B写入V

3.返回操作是否成功

此处的CAS是CPU提供的一条指令,通过这一条指令(原子操作)就完成了上述的三个步骤。

1、CAS的作用

1.基于CAS可以实现一些原子类:Java标准库中提供了一组原子类,针对常用的例如:int、long....进行了封装,可以基于CAS进行修改,并且线程安全

2.基于CAS实现自旋锁

2、ABA问题 

CAS中的关键就是先比较再交换。这里的比较其实是在比较当前值和旧值是否相同。如果这两个值相同就会认为中间并没有发生过变化。但是这里其实是有bug的,当前值和旧值相同,可能中间并没有发生变化,但还有一种极端情况就是中间变过,然后又变回来了。

 举个例子:

假设滑稽去取款机取钱,账户上有100,滑稽想要取出50.但是当他按下取款按钮时取款机卡了一下,于是他又按了一下取款(一次取钱操作执行了两次,我们预期的效果是取走50还剩50),下面我们来分析一下执行的过程:

1. t1线程将账户余额读取至寄存器与原值100比较

2. t1发现原值与账户余额相等

3. 执行CAS操作将账户余额修改为50,返回取款成功

4. t2线程将账户余额读取至寄存器与原值100比较

5. t2发现并不相同,于是啥也不干返回了FALSE

以上过程是滑稽正常取钱的情况下,但是当滑稽取钱的一刹那有一个新情况,有人给他的账户打了50块钱,这个时候问题就来了。

1. t1线程将账户余额读取至寄存器与原值100比较

2. t1发现原值与账户余额相等

3. 执行CAS操作将账户余额修改为50,返回取款成功

4. 账户被汇款50(具体就不展开了),账户余额又变成了100

5. t2线程将账户余额读取至寄存器与原值100比较

6. t2发现比较结果相同

7. 执行CAS操作将账户余额修改为50,返回取款成功 

实际上这里滑稽老铁是只先要取50的,但是却执行了两次取钱操作,这就是CAS的ABA问题

 那么我们该怎么去解决这个问题呢?

做法就是引入一个版本号,这个版本号只能增长不能减小,而每次比较我们比较的不再是原值而是版本号。因为版本号是不可逆的,因此也就避免了ABA问题。

三、synchronized的锁优化机制

1.锁膨胀/锁升级

这个机制体现了synchronized自适应的能力

 

 偏向锁:一开始是处于无锁状态的,当第一个线程加锁时就会进入偏向锁的状态。但偏向锁并不是真正的加锁,而是添加了一个标记(加锁解锁也是有消耗的)

自旋锁:如果有其他线程进入了锁竞争(竞争不激烈)就会进入轻量级锁的状态

重量级锁:如果锁竞争非常激烈,那么synchronized就会进入重量级锁状态

2、锁粗化 

这里的粗细是锁的粒度。加锁代码的范围越大那么锁的粒度就越粗,反之粒度就越细。

如果锁粒度粗,那么加锁解锁的开销就更小。如果锁越细,那么多个线程之间的并发性就更高。

通常编译器自动会有一个优化,如果某个地方粒度太细了那么它就会进行优化,让锁的粒度更粗一些,使代码执行更有效率。

3、锁消除 

有些代码是不需要加锁的,但是你给加上了锁。那么编译器会自动将这个锁去掉,减小开销,这就叫锁消除。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值