1:乐观锁的概念
乐观锁,顾名思义就是很乐观,我们认为通常情况下其它线程不会修改我们正在操作的数据(也就是不会发生并发冲突),所以不会上锁。但是为了保证线程安全,我们会在更新数据之前判断一下其它线程有没有修改我们的数据。可以使用版本号机制。
也就是说,乐观锁假设数据大概率不会发生并发冲突,所以只会在更新数据的时候检测是否发生了并发冲突,如果发现了并发冲突,则返回用户错误的信息,让用户决定怎么做。
乐观锁是一种常见的锁策略,它代表了一种关于锁的思想。而这种思想的核心可以概括为:冲突检测+数据更新。我们可以在下面的CAS实现乐观锁中深入体会这种思想。
2:乐观锁的实现-CAS(Compare And Swap 比较并交换)
首先声明,CAS是乐观锁技术,不是乐观锁。乐观锁是一种思想,而CAS是该思想的实现。
1.CAS基础逻辑
当多个线程都使用CAS来操作数据时,只会有一个线程成功修改数据,但其它修改失败的线程并不会挂起,而是会被告知本次竞争失败,可以再次尝试。
CAS的操作中包含三个操作数:V(当前内存中实际存储的值),A(我们预期内存中存储的值),B(我们要对内存数据进行修改的值)。我们会先让V与A比较,如果V==A,则我们认为内存中的数据没有被修改,并且将V的数据修改为B;否则我们认为内存中的数据已经被修改了,不做任何操作。不论哪种情况,我们都会在执行CAS操作之前返回V的值。也就是说,我认为V中应该包含A,如果包含,则将B放到这个位置,否则不要更改该位置,只要告诉我这个位置现在的值就好。这符合乐观锁的核心思想:冲突检测+数据更新。
我们可以结合源码来理解一下,下面截图是 AtomicInteger 类中的 compareAndSet()方法,该类使用的就是CAS技术
注释:
方法介绍:
方法参数:
方法返回值:
2.ABA问题及解决方法
(1):ABA问题
我们来看如下例子,来帮助我们理解ABA问题。
假设有这样一个链表
有线程T1,它已知该链表的 head==[A],它现在想删除节点[A],让B成为头节点。但此时T1的时间片用完了,轮到线程T2执行,而T2把整个链表都替换成了新的链表(如下图所示)
这时又该T1执行了,它判断 head==[A] 所以认为没有人修改该链表,于是它进行了该操作 head=[B],但B.next==null,这就相当于丢弃了T2修改后的链表数据,是线程不安全的。
(2):解决方法
我们可以使用 AtomicStampedReference 这个类 来解决ABA问题。
该类的部分注释:
根据官方注释我们可以看到,该类维护了一个对象的引用和一个整型(版本号),我们可以根据这两个数据综合来判断数据是否被修改过。
下面是该类实现的 compareAndSet()方法
注释:
方法介绍:
方法参数:
方法返回值:
注:本人是在校学生,对相关知识难免有疏漏或者理解错误,如有发现,还望指正,谢谢。