前言
乐观锁和悲观锁问题,是出现频率比较高的面试题。本文将由浅入深,逐步介绍它们的基本概念、实现方式(含实例)、适用场景,以及可能遇到的面试官追问,希望能够帮助你打动面试官。
目录
一、基本概念
二、实现方式(含实例)
三、优缺点和适用场景
四、面试官追问:乐观锁加锁吗?
五、面试官追问:CAS有哪些缺点?
六、总结
一、基本概念
乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题。
乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断一下在此期间别人是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。因此操作数据时直接把数据锁住,直到操作完成后才会释放锁;上锁期间其他人不能修改数据。
二、实现方式(含实例)
在说明实现方式之前,需要明确:乐观锁和悲观锁是两种思想,它们的使用是非常广泛的,不局限于某种编程语言或数据库。
悲观锁的实现方式是加锁,加锁既可以是对代码块加锁(如Java的synchronized关键字),也可以是对数据加锁(如MySQL中的排它锁)。
乐观锁的实现方式主要有两种:CAS机制和版本号机制,下面详细介绍。
1、CAS(Compare And Swap)
CAS操作包括了3个操作数:
-
需要读写的内存位置(V)
-
进行比较的预期值(A)
-
拟写入的新值(B)
CAS操作逻辑如下:如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。
这里引出一个新的问题,既然CAS包含了Compare和Swap两个操作,它又如何保证原子性呢?答案是:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。
下面以Java中的自增操作(i++)为例,看一下悲观锁和CAS分别是如何保证线程安全的。我们知道,在Java中自增操作不是原子操作,它实际上包含三个独立的操作:
-
读取i值;
-
加1;
-
将新值写回i
因此,如果并发执行自增操作,可能导致计算结果的不