乐观锁和悲观锁详解(面试必问必考)

乐观锁和悲观锁详解(面试必问必考)-mikechen

什么是乐观锁?

乐观锁是一种并发控制机制,用于处理多个事务同时操作相同数据的情况。

在乐观锁中假定并发操作不会引起冲突,因此不会立即阻塞其他操作,而是在提交操作之前检查数据是否被其他事务修改,所以这就是乐观锁由来。

 

乐观锁实现有哪些?

乐观锁一般会使用版本号机制、或 CAS 算法实现。

CAS算法的基本原理是:在操作共享资源之前,先比较当前值与预期值是否相等,如果相等,则将共享资源更新为新值,否则不进行任何操作。

如下图所示:

乐观锁和悲观锁详解(面试必问必考)-mikechen

CAS实现乐观锁的步骤如下:

  1. 首先读取数据的版本号或时间戳等值,并保存在本地变量中。
  2. 对数据进行修改,并将版本号或时间戳等值增加。
  3. 使用CAS操作将数据的版本号或时间戳等值和保存在本地变量中的值进行比较,如果相同,则说明数据没有被其他线程修改,将修改后的数据提交;否则说明数据已被其他线程修改,重新读取数据并进行相应的处理。

例如:更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

 

乐观锁有哪些问题?

乐观锁会出现ABA问题,即一个内存位置的值被修改了两次,但是两次修改的值和中间的值相同,从而导致CAS操作无法检测到值的变化。

解决ABA问题的方法通常是使用版本号、或时间戳机制。

大致流程如下:

  1. 在每次修改时,不仅更新值,还需要更新版本号或时间戳,确保每次修改都对应一个唯一的版本号或时间戳;
  2. 当进行检查时,不仅需要检查当前值是否与期望值相同,还需要检查当前版本号或时间戳是否与操作开始时的版本号或时间戳相同;
  3. 如果不同,则说明值已经被修改过,操作失败。

例如,当一个值原始值为A,版本号为1,第一次修改后变为B,版本号为2,第二次修改后又恢复为A,版本号为3,此时乐观锁会检测到版本号不匹配,从而防止ABA问题的发生。

 

什么是悲观锁?

悲观锁是一种传统的并发控制机制,它假设多个事务同时访问共享资源时,很可能会发生冲突,因此在访问共享资源时先将其锁定,保证只有一个事务可以访问共享资源,其他事务必须等待锁被释放后才能访问。

 

悲观锁实现有哪些?

悲观锁的实现通常有以下几种方式:

  1. 数据库锁:数据库提供了一种行级锁和表级锁机制,可以在事务中使用SELECT … FOR UPDATE语句或SELECT … LOCK IN SHARE MODE语句获取锁定记录或表。
  2. synchronized关键字:在Java中,使用synchronized关键字可以实现对象级别的悲观锁,保证在同一时刻只有一个线程可以访问该对象的关键代码段,其他线程必须等待锁被释放后才能访问。
  3. ReentrantLock类:Java中的ReentrantLock类也可以实现对象级别的悲观锁,提供了与synchronized相似的功能,并且支持可重入锁、公平锁、非公平锁等特性。

 

悲观锁有哪些问题?

悲观锁虽然可以确保数据的一致性和完整性,但也存在以下几个问题:

  1. 性能问题:由于悲观锁需要占用锁资源,其他线程在获取锁之前必须等待,因此在高并发场景下,锁的争用可能会导致性能瓶颈和资源浪费。
  2. 死锁问题:如果多个线程同时占用锁资源,并且按照不同的顺序获取锁,就可能会发生死锁,导致线程无法继续执行。
  3. 容易造成饥饿:如果某个线程在高并发情况下一直无法获取锁资源,就可能会导致饥饿现象,即该线程长时间无法执行,其他线程却能够执行。
  4. 难以维护:悲观锁的实现通常比较复杂,需要考虑锁的范围、粒度、超时等问题,同时还需要避免死锁和饥饿等问题,因此比较难以维护。

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法