首页天道酬勤乐观锁使用场景,悲观锁和乐观锁使用场景

乐观锁使用场景,悲观锁和乐观锁使用场景

张世龙 05-03 21:10 62次浏览

如何确保一种方法,或者如果一个代码是高并发的,则只能同时在一个线程上执行。 虽然可以使用与并发处理相关的API来控制单个APP应用程序,但是很明显,在单个APP应用程序体系结构演化为分布式微服务体系结构之后,进程之间的实例配置无法再通过APP应用层锁定机制来控制并发性。

那么,钥匙有什么类型? 你为什么要用钥匙? 钥匙的使用场景有哪些?

根据锁定类型的不同,对锁定APP应用程序场景的要求也不同。 首先,让我们来看看锁有什么种类,这些锁之间有什么区别。

悲观锁定。

Java的重量锁定同步

数据库行锁定

乐观摇滚

Java轻量级锁定volatile和CAS

数据库编号

分布式锁定(Redis锁定)

乐观摇滚就像你是一个生活态度乐观积极的人,总是朝着最好的情况去想。 例如,每次获取共享数据时,都会认为其他人不会修改而不进行锁定,但更新时会确定是否有人在此期间更新该数据。

乐观锁定在前使用,判断在后。 让我们来看看伪代码:

reduce ((选择total _ amountfromtable _ 1if ) total_amountamount ) ) returnfailed.) /其他业务逻辑更新total _ amount

由CAS算法实现的类是乐观锁。

悲观摇滚悲观摇滚是怎么理解的? 乐观的锁定正好相反,总是设想最坏的情况。 假设每次获取数据时都会被其他人修改,每次共享数据时都会锁定他,使用结束后再解锁,然后对其他人使用数据。

判断悲观的锁在前、后使用。 也看看伪代码:

reduce ()//其他业务逻辑intnum=update total _ amount=total _ amount-amountwheretotal _ amount amount; if(num==1(//业务逻辑. } ) }} Java的同步是重型锁定,是悲观锁定;

洛洛克是一种悲观的摇滚

操作示例这里列举一个非常常见的示例。 对于高合并,这也是资金账户的余额扣除,如余额扣除或商品库存扣除。 扣除操作会出现什么问题? 为清楚起见,可能出现的问题已扣除为扣减导致的超卖,即负数。

例如,我的库存数据只有100个。 合并后,第一次要求销售100个,第二次要求销售100元,现在的库存数量为负。 遇到这种场面该怎么解读? 这里列举四个方案。

方案1 )同步排他锁定此时,很容易想到最简单的方案(http://www.Sina.com/(synchronize ) )。 但是排他锁的缺点很明显:

一个缺点是线程串行带来的性能问题,性能消耗很大。

另一个缺点是无法解决分布式部署时进程之间的问题。

案例2 :数据库行锁定其次,您可能会考虑用数据库行锁定锁定此数据。 与独占锁相比,此方案解决了交叉流程问题,但仍然存在缺点。

一个缺点是性能问题,在数据库级别,在提交事务之前会被阻止,并且在此处也会串行执行

第二个注意事项是将事务的隔离级别设置为读提交。 否则,在并发情况下,其他事务将看不到提交的数据,这仍然可能导致溢出问题。

缺点三是数据库连接容易填满。 如果在事务过程中与第三方接口进行交互,则可能发生超时。 此事务的连接将保持被阻止,数据库连接将填充。

最后的缺点是容易发生交叉死锁,如果多业务的锁定控制不好,就会发生ab2记录的交叉死锁。

方案3 :由于redis分布式锁定之前的方案本质上使用数据库作为分布式锁定,因此出于相同的原因,redis,zookeeper相当于一种数据库锁定。 实际上,遇到锁定问题时,代码本身在synchronize和各种lock中使用也很复杂,所以我们考虑交给能够解决代码处理完整性问题的专业组件

在此分析分布式锁的优缺点。

好处:

同步排它锁

缺点:

锁定设定和超时时间的原子性设定

不设置超时时间的缺点;

如果服务停机或线程阻塞超时

超时时间的设定不合理时

避免大量对数据库排他锁的征用,提高系统的响应能力

redis锁定命令setnx的expire用于设置锁定到期日,而del用于解锁锁定。但是,在2.6.12和更早版本中,用于设置锁定到期日的命令有两个操作如果setnx设置密钥值后,还没有时间使用expi

re来设置过期时间,当前线程挂掉了或者线程阻塞,会导致当前线程设置的key一直有效,后续的线程无法正常使用setnx获取锁,导致死锁。

针对这个问题,redis2.6.12以上的版本增加了可选的参数,可以在加锁的同时设置key的过期时间,保证了加锁和过期操作原子性的。

但是,即使解决了原子性的问题,业务上同样会遇到一些极端的问题,比如分布式环境下,A获取到了锁之后,因为线程A的业务代码耗时过长,导致锁的超时时间,锁自动失效。后续线程B就意外的持有了锁,之后线程A再次恢复执行,直接用del命令释放锁,这样就错误的将线程B同样Key的锁误删除了。代码耗时过长还是比较常见的场景,假如你的代码中有外部通讯接口调用,就容易产生这样的场景。

设置合理的时长

刚才讲到的线程超时阻塞的情况,那么如果不设置时长呢,当然也不行,如果线程持有锁的过程中突然服务宕机了,这样锁就永远无法失效了。同样的也存在锁超时时间设置是否合理的问题,如果设置所持有时间过长会影响性能,如果设置时间过短,有可能业务阻塞没有处理完成,是否可以合理的设置锁的时间?

续命锁

这是一个很不容易解决的问题,不过有一个办法能解决这个问题,那就是续命锁,我们可以先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间之后重新去设置这个锁的超时时间,续命锁的实现过程就是写一个守护线程,然后去判断对象锁的情况,快失效的时候,再次进行重新加锁,但是一定要判断锁的对象是同一个,不能乱续。

同样,主线程业务执行完了,守护线程也需要销毁,避免资源浪费,使用续命锁的方案相对比较而言更复杂,所以如果业务比较简单,可以根据经验类比,合理的设置锁的超时时间就行。

方案4:数据库乐观锁

数据库乐观锁加锁的一个原则就是尽量想办法减少锁的范围。锁的范围越大,性能越差,数据库的锁就是把锁的范围减小到了最小。我们看下面的伪代码

reduce(){    select total_amount from table_1    if(total_amount < amount ){          return failed.      }      //其他业务逻辑    update total_amount = total_amount - amount;  }

我们可以看到修改前的代码是没有where条件的。修改后,再加where条件判断:总库存大于将被扣减的库存。

update total_amount = total_amount - amount where total_amount > amount

如果更新条数返回0,说明在执行过程中被其他线程抢先执行扣减,并且避免了扣减为负数。

但是这种方案还会涉及一个问题,如果在之前的update代码中,以及其他的业务逻辑中还有一些其他的数据库写操作的话,那这部分数据如何回滚呢?

我的建议是这样的,你可以选择下面这两种写法:

利用事务回滚写法:

我们先给业务方法增加事务,方法在扣减库存影响条数为零的时候扔出一个异常,这样对他之前的业务代码也会回滚。

reduce(){    select total_amount from table_1    if(total_amount < amount ){          return failed.      }      //其他业务逻辑    int num = update total_amount = total_amount - amount where total_amount > amount;   if(num==0) throw Exception;}

第二种写法

reduce(){    //其他业务逻辑    int num = update total_amount = total_amount - amount where total_amount > amount;    if(num ==1 ){          //业务逻辑.      }  else{    throw Exception;  }}

首先执行update业务逻辑,如果执行成功了再去执行逻辑操作,这种方案是我相对比较建议的方案。在并发情况下对共享资源扣减操作可以使用这种方法,但是这里需要引出一个问题,比如说万一其他业务逻辑中的业务,因为特殊原因失败了该怎么办呢?比如说在扣减过程中服务OOM了怎么办?

我只能说这些非常极端的情况,比如突然宕机中间数据都丢了,这种极少数的情况下只能人工介入,如果所有的极端情况都考虑到,也不现实。我们讨论的重点是并发情况下,共享资源的操作如何加锁的问题。

总结

最后我来给你总结一下,如果你可以非常熟练的解决这类问题,第一时间肯定想到的是:数据库版本号解决方案或者分布式锁的解决方案;但是如果你是一个初学者,相信你一定会第一时间考虑到Java中提供的同步锁或者数据库行锁。

来源 | ii081.cn/g3lduQ

推荐阅读

掌握Mybatis动态映射,我可是下了功夫的

2020所有原创

写给大忙人看的JAVA核心技术.pdf下载

图解多线程

给,我私藏的26道MyBatis面试题~

搞定这24道JVM面试题,要价30k都有底气~

Mybatis 中xml和注解映射,so easy啦

redis分布式锁释放锁,redis分布式锁实现方式