JAVA多线程(三)

目录

前言

一、悲观锁

二、乐观锁

乐观锁的实现

三、悲观锁和乐观锁对比

四、CAS算法存在的问题

总结




前言

前面我们了解了线程安全问题的解决方法,现在我们来讲讲悲观锁和乐观锁




一、悲观锁

顾名思义悲观锁就是认为线程的安全问题非常容易出现,对代码进行上锁。上篇文章所讲的锁机制都属于悲观锁。悲观锁的锁定和释放需要消耗比较多的资源,降低程序的性能。



二、乐观锁

乐观锁默认情况下认为数据是不会发生冲突的,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误信息,让用户决定如何去做。

乐观锁的实现

1.版本号机制

利用版本号记录数据更新的次数,一旦更新版本号加1,线程修改数据后会判断版本号是否是自己更新的次数,如果不是就不更新数据。

2.CAS 算法
即 compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步,所以也叫非阻塞同步。CAS 算法涉及到三个操作数:

需要读写的内存值 V
进行比较的值 A
拟写入的新值 B
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作。一般情况下,这是一个自旋操作,即不断的重试。

三、悲观锁和乐观锁对比

1.悲观锁更加重量级,占用资源更多,应用线程竞争比较频繁的情况,多写少读的场景

2.乐观锁更加轻量级,性能更高,应用于线程竞争比较少的情况,多读少写的场景

四、CAS算法存在的问题

CAS机制最大的问题就是ABA问题。

假设内存中有一个值为A的变量,存储在地址V中。

此时有三个线程想使用CAS的方式更新这个变量的值,每个线程的执行时间有略微偏差。线程1和线程2已经获取当前值,线程3还未获取当前值。

接下来,线程1先一步执行成功,把当前值成功从A更新为B;同时线程2因为某种原因被阻塞住,没有做更新操作;线程3在线程1更新之后,获取了当前值B。

 之后,线程2仍然处于阻塞状态,线程3继续执行,成功把当前值从B更新成了A。

 最后,线程2终于恢复了运行状态,由于阻塞之前已经获得了“当前值A”,并且经过compare检测,内存地址V中的实际值也是A,所以成功把变量值A更新成了B。

 看起来这个例子没啥问题,但如果结合实际,就可以发现它的问题所在。

我们假设一个提款机的例子。假设有一个遵循CAS原理的提款机,小灰有100元存款,要用这个提款机来提款50元。

由于提款机硬件出了点问题,小灰的提款操作被同时提交了两次,开启了两个线程,两个线程都是获取当前值100元,要更新成50元。

理想情况下,应该一个线程更新成功,一个线程更新失败,小灰的存款值被扣一次。

线程1首先执行成功,把余额从100改成50.线程2因为某种原因阻塞。这时,小灰的妈妈刚好给小灰汇款50元。

线程2仍然是阻塞状态,线程3执行成功,把余额从50改成了100。

线程2恢复运行,由于阻塞之前获得了“当前值”100,并且经过compare检测,此时存款实际值也是100,所以会成功把变量值100更新成50。

原本线程2应当提交失败,小灰的正确余额应该保持100元,结果由于ABA问题提交成功了。

怎么解决呢?加个版本号就可以了。这样在compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致。



总结

以上就是今天要讲的内容,本文简单介绍了悲观锁和乐观锁。欲知后事如何,且听下回讲解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值