一:CAS原理
乐观锁的本质即是CAS,操作系统提供了支持CAS修改内存值的原子指令,所以乐观锁得以实现。
CAS(CompareAndSwap)比较和替换(CPU级别的指令操作):通俗的理解就是 CAS 操作需要我们提供一个预期值,当预期值与当前线程的变量值相同时,说明还没线程修改该值,当前线程可以进行修改,也就是执行 CAS 操作。但如果预期值与当前线程不符,则说明该值已被其他线程修改,此时不执行更新操作。但可以选择重新读取该变量尝试再次修改该变量(自旋),也可以放弃操作。(自旋的次数实际上可以通过启动参数来配置,如果你不配置的话,默认是10,所以不会出现死循环)
(那么我们会发现基于这个CAS原理,在并发量很高的情况下很容易就会比较失败。所以基于CAS的原子类并不适用于任何场景,在并发很高,竞争很激烈的情况下就不适合再使用了,可以考虑加锁或其它的方式来解决。)
CAS底层实现:sun.misc.Unsafe 这个类提供了如下3个CAS方法
/**
* JDK源码
*/
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
obj 和 valueOffset:表示这个共享变量的内存地址。这个共享变量是obj对象的一个成员属性,valueOffset表示这个共享变量在obj类中的内存偏移量。所以通过这两个参数就可以直接在内存中修改和读取共享变量值。
expect: 表示预期原来的值。
update: 表示期待更新的值。
分析一下compareAndSwapInt()方法的四个参数:
var1代表AtomicInteger实例对象;
var2代表地址偏移量valueOffset (用来定位 value 在 AtomicInteger 对象中的位置);
var4代表预期值expect;
var5代表更新值update。
扩展:
为什么叫Unsafe类呢??
观察发现JUC中大量使用了sun.misc.Unsafe 这个类,但官方却不建议开发者使用
很显然方法都是用native来修饰的,这就表明这个方法不是由Java来实现的。而是调用底层的C或其他接口来实现的。所以由此可见CAS是一个CPU级别的指令操作。
其实Unsafe是用于在实质上扩展Java语言表达能力、便于在更高层(Java层)代码里实现原本要在更低层(C层)实现的核心库功能用的。这些功能包括裸内存的申请/释放/访问,低层硬件的atomic/volatile支持,创建未初始化对象等。它原本的设计就只应该被标准库使用。
建议看这个知乎帖子第一楼R大的回答:为什么JUC中大量使用了sun.misc.Unsafe 这个类,但官方却不建议开发者使用
二:ABA问题
如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号,在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。
基于这种解决思路:
atomic包里提供了AtomicStampedReference和AtomicMarkableReference类来解决ABA问题。