乐观锁:操作数据的时候,乐观锁不会认为别人同时也会修改数据,乐观锁本身是不会加锁的,只是在执行更新操作的时候,会检查在这个期间是否有其他人修改数据,如果有则停止操作,否则的话继续执行操作
悲观锁:操作数据的时候,悲观锁认为别人同时也会修改数据,所以悲观锁在操作数据的时候会给数据加锁,在加锁期间别的线程不能访问该数据,只有等到操作完成后锁被释放掉
悲观锁主要是通过加锁实现,既可以是对数据加锁也可以是对代码加锁,而乐观锁的实现机制通常主要是 CAS 机制 和 版本号机制
CAS 操作主要是包含有三个操作项:1.需要读写的内存位置 2.进行比较的预期值 3.即将写入的新值,当内存位置上的值和预期值相等的话,则该内存位置上的值更新成将要写入的新值,否则不进行任何操作,一般 CAS 操作都是自旋式的,如果执行失败的话,则会重新尝试,直到成功为止
另外版本号机制也能实现乐观锁,版本号的基本思想是在数据中新增一个字段 version,该字段表示数据的版本号,当对数据修改的时候该值对应的版本号 +1,当某个线程查询数据时,同时也将该值对应的版本号查询出来,当该线程对值进行修改的时候需要判断一下当前版本号是否和之前读取的版本号一致,如果一致的话,则进行修改操作
如果乐观锁和悲观锁都可以使用的时候,需要考虑下竞争的激烈程度(并发冲突的概率大小):
1.当竞争激烈比较小的时候,也就是并发冲突出现的概率比较小,这个时候乐观锁比较占据优势,因为悲观锁会对数据或者代码块加锁,影响并发,并且加锁和释放锁会消耗资源
2.当竞争激烈比较大的时候,也就是并发冲突出现的概率比较大,这个时候悲观锁比较占据优势,因为大多数的 CAS 都是自旋式的,当 CAS 操作执行失败的时候,会一直尝试,对 CPU 的性能消耗比较大
CAS 机制的缺点:
1.ABA 问题
假设我们有两个线程:线程1,线程2
(1) 线程 1 读取到内存中的数据为 A
(2) 线程 2 修改该值 为 B
(3) 线程 2 修改该值为 A
(4) 线程 1 对该值执行 CAS 操作
因为此时内存中的这个值为 A,所以第四步中 线程 1 执行 CAS 操作成功了,但是实际上线程 2 已经对该值执行了修改操作,所以,针对 ABA 问题,比较有效的机制是引入版本号,当执行 CAS 操作的时候,除了比较内存中的值外,还要比较版本号是否一样,如果都没有改变的话,则 CAS 执行成功
2.高并发开销
大多数 CAS 操作都是自旋式的,当在高并发环境下,CAS 操作执行失败的时候会一直尝试,造成 CPU 性能消耗,针对这个问题可以引入失败退出机制,当重试的次数超出某个阈值的时候,则自动退出,但是尽量避免在高并发环境下使用 乐观锁
乐观锁会加锁吗?
1.乐观锁本身是不会加锁的,只是在执行更新数据前会判断下在这个期间是否有其他线程更新数据
2.有时候会有乐观锁和其他锁合作的情况,比如 mysql 执行 update 操作的时候,不过这只是乐观锁和其他锁合作的一种情况,并不能改变乐观锁本身不会加锁的情况
网友评论