介绍
望文生义,乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定是好是坏。
悲观锁
共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
白话理解:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
乐观锁
每次去拿数据的时候都认为其他进程不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别进程有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。如:处理扣款、库存等场景都会应用此方式的锁。
使用场景
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁实现方式
乐观锁一般会使用版本号机制或CAS算法实现,下面对两种方式分别进行介绍
版本号机制
一般是在数据表中加上一个数据版本号ver字段,表示数据被修改的次数,当数据被修改时,ver值会加1。当线程A要更新数据值时,在读取数据的同时也会读取ver值,在提交更新时,若刚才读取到的ver值为当前数据库中的ver值相等时才更新,否则重试更新操作,直到更新成功。
举一个简单的例子: 数据库中产品X库存信息表中有一个 ver 字段,当前值为 1,而产品X 库存为 9
- 操作员 A 进行发货处理,会从库中取得产品 X ver = 1,并从库存中 9-5
- 在操作员 A 发货过程中,操作员 B 也进行发货操作 从库中取得产品 X ver = 1,并从库存中 9-6
- 操作员 A 完成了发货工作,将数据版本号( ver=1 ),连产品库存扣除后( 4 ),提交至数据库更新,此时由于提交数据版本等于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
- 操作员 B 完成后,也将版本号( ver=1 )试图向数据库提交数据( 3 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 1 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须等于当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
5
这样,就避免了操作员 B 用基于 ver=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能
CAS(比较并交换)
CAS 有 3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
V 可以理解为当前版本号
A 初次读取的版本号,与内存V对比
B 操作成功是更新的版本号