什么是锁🔒?
从传统的铁锁到现代的密码锁、指纹锁等,锁的便捷性和安全性在不断提升,对于私有财产或领地的保护也更加高效和健全。在计算机信息世界里,单机单线程时代没有锁的概念。自从出现了资源竞争,人们才意识到需要对部分场景的执行现场加锁,昭告天下,表明自己的“短暂”拥有(其实对于任何有形或无形的东西,拥有都不可能是永恒的)。计算机的锁也是从开始的悲观锁,发展到后来的乐观锁、偏向锁、分段锁等。
锁主要提供了两种特性:互斥性和不可见性。因为锁的存在,某些操作对外界来说是黑箱操作,只有锁的持有者才知道对变量进行了什么修改。
悲观锁:先取锁再访问
一、悲观锁的概念
顾名思义,就是很悲观的锁🔒。
总是假设最坏的情况,每次拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完后再把资源转让给其他线程)。传统的关系型数据库就用到了这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
二、悲观锁的实现
场景:有用户A和用户B在同一家店去购买同一个商品,但是商品的可购买数量只有一个
表中的数据如图所示:
不加锁:用户A和用户B同时下单,就会报错
加锁描述:
-
加入当用户A对下单购买商品(臭豆腐)的时候,先去尝试对该数据(臭豆腐)加上悲观锁
-
加锁失败:说明商品(臭豆腐)正在被其他事务进行修改,当前查询需要等待或者抛出异常,具体返回的方式需要由开发者根据具体情况去定义
-
加锁成功:对商品(臭豆腐)进行修改,也就是只有用户A能买,用户B想买(臭豆腐)就必须一直等待。当用户A买好后,用户B再想去买(臭豆腐)的时候会发现数量已经为0,那么B看到后就会放弃购买
-
在此期间如果有其他对该数据(臭豆腐)做修改或加锁的操作,都会等待我们解锁后或者直接抛出异常
乐观锁
一、乐观锁的概念
每次去拿数据的时候认为别人不会修改,所以不会上锁。但是如果想要更新数据,则会在更新前检查在读取至更新这段时间中别人有没有修改过这个数据(可使用版本号机制和CAS算法实现)。如果修改过,则重新读取,再次尝试更新,循环上述步骤直到更新成功(或更新失败的线程放弃操作)。乐观锁使用于多读的应用类型,可以提高吞吐量。
二、乐观锁的实现
场景:有用户A和用户B在同一家店去购买同一个商品,但是商品的可购买数量只有一个
表中的数据如图所示:
加锁描述:
-
首先用户A和用户B同时将臭豆腐(id=2)的数据查出来
-
然后用户A先买,用户A将(id=1和version=0)作为条件进行数据更新,将数量-1,并且将版本号+1。此时版本号变为1。用户A此时就完成了商品的购买
-
用户B开始买,用户B也将(id=1和version=0)作为条件进行数据更新
-
更新完后,发现更新的数据行数为0,此时就说明已经有人改动过数据,此时就应该提示用户B重新查看最新数据购买
如何选择?
悲观锁阻塞事务、乐观锁回滚重试,各有优缺点。像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。
注:
-
乐观锁并未真正加锁,一旦锁的粒度掌握不好,更新失败的概率会比较高
-
悲观锁依赖数据库锁,效率低,更新失败概率比较低
好啦,就先到这里吧~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信~