龙空技术网

面试:一文彻底搞懂乐观锁与悲观锁

墨林码农 280

前言:

目前小伙伴们对“乐观和悲观锁的区别”大体比较关注,姐妹们都想要了解一些“乐观和悲观锁的区别”的相关文章。那么小编同时在网络上汇集了一些关于“乐观和悲观锁的区别””的相关知识,希望各位老铁们能喜欢,看官们一起来学习一下吧!

大家好,我是墨林,金三银四的季节已过去三分之一,不知道大家对2023年的面试有没有什么看法?是否已经找到自己心仪的工作?经常听大家说不了解常见的悲观锁和乐观锁的思想,今天小编给大家分享一道常见的面试题,用最通俗的方式给大家解释一下。

全文目录

1.什么是悲观锁

2.什么是乐观锁

3.典型案例

4.使用场景

1.什么是悲观锁?

顾名思义,悲观锁是基于一种悲观的态度类来防止一切数据冲突,它是以一种预防的姿态在修改数据之前把数据锁住,然后再对数据进行读写,在它释放锁之前任何人都不能对其数据进行操作,直到前面一个人把锁释放后下一个人数据加锁才可对数据进行加锁,然后才可以对数据进行操作,一般数据库本身锁的机制都是基于悲观锁的机制实现的。

特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高。

手动加悲观锁:

(1)读锁LOCK tables test_db read

(2)释放锁UNLOCK TABLES

(3)写锁:LOCK tables test_db WRITE

(4)释放锁UNLOCK TABLE

2.什么是乐观锁?

乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个任务可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现)。

特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁通而是通过业务实现锁的功能,不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提高操作的性能,不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能却反而不如悲观锁。

乐观锁实现形式:

表A 字段

现在有两个请求同时操作表A

操作1先查询出需要检索的数据为:

操作2也查询出需要检索的数据为:

操作1把Name字段数据修改成lisi 比操作2先提交;

此时提交操作1会把之前查询到的version与现在的数据的version进行比较,版本相同则可以提交,版本不同则视为数据过期:

SQL语句:update A set Name=lisi,version=version+1 where ID=#{id} and version=#{version}

此时数据库变成了如下:

那么最后操作2修改了数据来提交的时候是以操作1同样的方式,当然最后肯定不能提交成功,因为他手里的版本号和现在数据库里的版本号不匹配

3.典型案例

悲观锁:synchronized 关键字和 Lock 接口

Java 中悲观锁的实现包括 synchronized 关键字和 Lock 相关类等,我们以 Lock 接口为例,例如 Lock 的实现类 ReentrantLock,类中的lock() 等方法就是执行加锁,而 unlock() 方法是执行解锁。处理资源之前必须要先加锁并拿到锁,等到处理完了之后再解开锁,这就是非常典型的悲观锁思想。

乐观锁:原子类

乐观锁的典型案例就是原子类,例如 AtomicInteger 在更新数据时,就使用了乐观锁的思想,多个线程可以同时操作同一个原子变量。

大喜大悲:数据库

数据库中同时拥有悲观锁和乐观锁的思想。例如,我们如果在 MySQL 选择 select for update 语句,那就是悲观锁,在提交之前不允许第三方来修改该数据,这当然会造成一定的性能损耗,在高并发的情况下是不可取的。

相反,我们可以利用一个版本 version 字段在数据库中实现乐观锁。在获取及修改数据时都不需要加锁,但是我们在获取完数据并计算完毕,准备更新数据时,会检查版本号和获取数据时的版本号是否一致,如果一致就直接更新,如果不一致,说明计算期间已经有其他线程修改过这个数据了,那我就可以选择重新获取数据,重新计算,然后再次尝试更新数据。

说法

有一种说法认为,悲观锁由于它的操作比较重量级,不能多个线程并行执行,而且还会有上下文切换等动作,所以悲观锁的性能不如乐观锁好,应该尽量避免用悲观锁,这种说法是不正确的。

因为虽然悲观锁确实会让得不到锁的线程阻塞,但是这种开销是固定的。悲观锁的原始开销确实要高于乐观锁,但是特点是一劳永逸,就算一直拿不到锁,也不会对开销造成额外的影响。

反观乐观锁虽然一开始的开销比悲观锁小,但是如果一直拿不到锁,或者并发量大,竞争激烈,导致不停重试,那么消耗的资源也会越来越多,甚至开销会超过悲观锁。

所以,同样是悲观锁,在不同的场景下,效果可能完全不同,可能在今天的这种场景下是好的选择,在明天的另外的场景下就是坏的选择,这恰恰是“汝之蜜糖,彼之砒霜”。

因此,我们就来看一下两种锁各自的使用场景,把合适的锁用到合适的场景中去,把合理的资源分配到合理的地方去。

4.两种锁各自的使用场景

悲观锁适合用于并发写入多、临界区代码复杂、竞争激烈等场景,这种场景下悲观锁可以避免大量的无用的反复尝试等消耗。

乐观锁适用于大部分是读取,少部分是修改的场景,也适合虽然读写都很多,但是并发并不激烈的场景。在这些场景下,乐观锁不加锁的特点能让性能大幅提高。

最后

几个简单的字符,就能创造出欢乐,几个简单的号码,便能写出奇迹。

一个键盘,就能畅游世界,一根网线,便能知晓天下。

创作不易,感谢大家的支持,喜欢的老铁加个关注,点个赞。后续也会分享更多的干货和技术资讯,您的阅读就是对小编最大支持,再次感谢各位老铁!

标签: #乐观和悲观锁的区别 #悲观锁与乐观锁的现实用法 #悲观锁与乐观锁的现实用法是什么