乐观锁和悲欢锁的区别_乐观锁_乐观锁和悲欢锁的实现

一、什么是乐观锁和悲观锁

乐观锁和悲观锁主要使用在并发的情况下,在多个事务中共同访问同一个数据库资源,为了避免因为同时访问造成的数据操作错误而产生。这里从两方面说,第一指的是数据库,第二是java.

数据库中的乐观锁和悲观锁

乐观锁,主要强调的是每次取数据的时候,都认为别的线程或事务不会修改数据,所以不会对数据进行上锁,只有在更新数据的时候才会判断是否有线程对要操作的数据进行修改;

悲观锁,主要强调的是每次取数据的时候,都认为别的线程会修改数据,所以每次都会对数据上锁,只有获取锁的情况下才有操作数据的机会;

从上面的描述中,可看出数据库层面的乐观锁和悲观锁,主要是在集中在对sql的处理上,即反映在数据库的能力上。

java中的乐观锁和悲观锁

对应于乐观锁和悲观锁的逻辑,即对资源的占用情况,在java中同时有对应的表现,如,关键字、等,都是独占资源的情况,所以属于悲观锁;CAS操作属于乐观锁;

二、乐观锁和悲观锁的具体实现

这里的具体实现以数据库层面的为主,java中并发的情况下次在写。

以一个具体的例子来说明乐观锁和悲观锁的具体使用。

有商品表(),其表结构如下,

此表只有两列商品名称()、商品数量(p_num),以商城秒杀系统减库存场景来说明。假设,现在要对为“手表”的库存进行更新,现在有多个线程都要进行减库存操作。

1)、未使用锁

首先,要从数据库中查出当前的库存,然后把库存减去,即=-;

select p_num from products where p_name ='手表';
update products set p_num=p_num_new where p_name ='手表';

上面的语句在单线程低并发下不会有问题,但在多线程高并发下,假如,两个线程T1、T2读到同一个,又同时去减库存,那么它们计算到的最新的库存为,

为线程T1减的库存数,为线程T2减的库存数,

T1:=-;

T2:=-;

从上边看出,T2的库存数计算的是有问题的,应该是:=–

那么上边的数据就是错误。

2)、乐观锁

实现乐观锁有两种方式,即和CAS。下面以上面的例子一一说明

2.1、,数据版本号,

需要在原有数据表结构中新增一列,作为版本号,其数据类型可为类型,其作用主要体现在更新过程中,代表数据被更新的次数,每成功更新一次加1;

其商品表()结构如下,

乐观锁和悲欢锁的实现_乐观锁_乐观锁和悲欢锁的区别

首先,要从数据库中查出当前的库存和(记为),然后把库存减去,即=-;

select p_num,version from products where p_name ='手表';
update products set p_num=p_num_new,,version=version+1 where p_name ='手表' and 
version=#{version_old};

上面sql中新增了条件,即使用取出的作为更新的条件。看在两个线程下的过程,

假如,两个线程T1、T2读到同一个和(记为),又同时去减库存,那么它们计算到的最新的库存为,

为线程T1减的库存数,为线程T2减的库存数,

T1:=-;

T2:=-;

再看T1和T2的更新过程,假如T1先执行,T1更新成功后的值记为

update products set p_num=p_num_t1,,version=version+1 where p_name ='手表' and 
version=#{version_old};

T2的更新为,

update products set p_num=p_num_t1,,version=version+1 where p_name ='手表' and 
version=#{version_old};

T1更新成功后,T2还会成功吗?肯定不会,因为此时的执行了加1操作,变成了=+1,而T2的条件中的依旧为,即+1≠,此时T2是不会执行成功,那么这样的话数据就不会造成混乱。

在执行更新操作的时候,只有现在数据的版本号,和读出的版本号一致,才更新成功,否则更新失败;

2.2、CAS操作

CAS中包含三个值,内存值V,现在值A,新值B,只有在保证V=A的情况下,才会把V更新为B;应用到数据库方面,V代表的是首先读出的值;

以上面的例子说明,

要从数据库中查出当前的库存,然后把库存减去,即=-;

select p_num,version from products where p_name ='手表';
update products set p_num=p_num_new,,version=version+1 where p_name ='手表' and 
p_num=#{p_num1};

假如,两个线程T1、T2读到同一个,又同时去减库存,那么它们计算到的最新的库存为,

为线程T1减的库存数,为线程T2减的库存数,

T1:=-;

T2:=-;

再看T1和T2的更新过程,假如T1先执行,T1更新成功后的p_num变成了-,

update products set p_num=p_num_new where p_name ='手表' and p_num=#{p_num1}

T2再执行,

update products set p_num=p_num_new where p_name ='手表' and p_num=#{p_num1}

此时数据中的p_num值已经变成了-,那么where条件就不成立,那么更新便不会成功。

通过在更新时比较现在的值和之前的值是否一致,来判断是否可更新成功,其原理类似于。但此种方式无法避免ABA的问题,即,如果p_num被更新过,且正好更新为了,使用CAS的方式是可以更新成功的,但最终的结果是一致的。

2)、悲观锁

即,对数据库的所有操作均加上数据库锁,其实现主要依赖于数据层面,

* from for ; 对查询结果的每行均加排他锁

悲观锁能够防止丢失更新和不可重复读这类问题,但是它非常影响并发性能,因此应该谨慎使用

三、总结

乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大系统的整体吞吐量。但如果经常产生冲突,上层应用会不断的进行retry,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适,即对所有的操作加锁控制。

———END———
限 时 特 惠: 本站每日持续更新海量各大内部创业教程,永久会员只需109元,全站资源免费下载 点击查看详情
站 长 微 信: nanadh666

声明:1、本内容转载于网络,版权归原作者所有!2、本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。3、本内容若侵犯到你的版权利益,请联系我们,会尽快给予删除处理!