正文
一、什么是CAS
由于JVM的synchronized重量级锁涉及操作系统内核态下互斥锁的使用,因此其线程阻塞和唤醒都涉及进程在用户态和内核态频繁的切换,导致重量级锁开销大,性能低。CAS,Compare And Swap比较并替换。CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(E)新值(N)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。CAS也称为自旋锁,在一个(死)循环[for(;;)]里不断进行CAS操作,直到成功为止(自旋操作),实际上,CAS也是一种乐观锁。
Unsafe 提供的CAS方法
public final native boolean compareAndSetReference(Object o, long offset, Object expected, Object x); public final native boolean compareAndSetLong(Object o, long offset, long expected, long x); public final native boolean compareAndSetLong(Object o, long offset, long expected, long x);
参数说明
o- 需要操作字段所在的对象
offset-需要操作字段的偏移量 相对对象头,在64位jvm虚拟机偏移量是12,因为markWord占64位,Class pointer(类对象指针)占32位,所以偏移量是12.
expected-期望值,也就是旧值
x-更新的值,也就是新值
在执行Unsafe的CAS方法时,这些方法值首先将内存位置的值与旧值比较,如果匹配那么CPU会自动将内存位置的值更新为新值,并返回true,如果不匹配,CPU不做任何操作,并返回false。当并发修改的线程少,冲突出现的机会少时,自旋次数也会减少,CAS的性能就会很高,反之冲突越多,自旋的次数越多,CAS的性能就会越低。
二、JUC原子类
以AtomicInteger为例
public final int get(); //获取当前的值 public final int getAndSet(int newValue); //获取当前的值,然后设置新的值 public final int getAndIncrement() ;//获取当前的值,然后自增 public final int getAndDecrement() ; //获取当前的值,然后自减 public final int getAndAdd(int delta) ; //获取当前的值,并加上预期的值 boolean compareAndSet(int expect, int update);//通过CAS方式设置整数值
public class AtomicIntegerDemo extends Thread { private AtomicInteger atomicInteger; AtomicIntegerDemo(AtomicInteger atomicInteger) { this.atomicInteger = atomicInteger; } @Override public void run() { for (int i = 0; i < 100000; i++) { atomicInteger.getAndIncrement(); } } public static void main(String[] args) throws InterruptedException { AtomicInteger atomicInteger = new AtomicInteger(0); AtomicIntegerDemo demo1 = new AtomicIntegerDemo(atomicInteger); AtomicIntegerDemo demo2 = new AtomicIntegerDemo(atomicInteger); AtomicIntegerDemo demo3 = new AtomicIntegerDemo(atomicInteger); demo1.start(); demo2.start(); demo3.start(); demo1.join(); demo2.join(); demo3.join(); System.out.println(atomicInteger.get());//300000 } }
由上面执行结果可知AtomicInteger是一个原子的操作,并发操作是线程安全的。AtomicInteger主要通过CAS自旋+volatile的方案实现,既保障了变量操作的线程安全性,又避免了synchronized重量级锁的开销。
三、ABA问题
使用CAS操作内存数据时,数据发生过变化也能更新成功,如A——>B——>A,最后一个CAS预期数据A实际已经发生过更改,但也能修改成功,这就产生了ABA的问题。ABA的解决思路一般是使用版本号,每次变更都带上版本号。JDK提供了两个类AtomicStampedReference和AtomicMarkableReference解决ABA的问题。
AtomicStampedReference在CAS的基础上增加了一个Stamp(印戳或者标记),使用这个标识可以判断数据是否发生变化。
public static void main(String[] args) { String str1 = "aaa"; String str2 = "bbb"; //初始化AtomicStampedReference 初始值是aaa,印戳是1 AtomicStampedReference<String> reference = new AtomicStampedReference<String>(str1, 1); //cas比较str1=aaa ,reference.getStamp()=1,就把str2的值更新到新值,印戳加1 reference.compareAndSet(str1, str2, reference.getStamp(), reference.getStamp() + 1); //当前的值为:bbb印戳是:2 System.out.println("当前的值为:" + reference.getReference() + "印戳是:" + reference.getStamp()); boolean flag = reference.weakCompareAndSet(str2, "ccc", 2, reference.getStamp() + 1); //更新后的值为:bbb是否更新成功:false,更新失败,因为印戳4和2比较不对 System.out.println("更新后的值为:" + reference.getReference() + "是否更新成功:" + flag); }
AtomicMarkableReference是AtomicStampedReference的简化版,不关心修改过几次,只关心是否修改过,标志属性是boolean类型,其值只记录是够修改过。
public static void main(String[] args) { AtomicMarkableReference<Integer> atomicMarkableReference = new AtomicMarkableReference<Integer>(1, true); boolean b = atomicMarkableReference.compareAndSet(1, 2, true, false); //是否更新成功true更新后的值:2更新后的标志 mark:false System.out.println("是否更新成功" + b + "更新后的值:" + atomicMarkableReference.getReference() + "更新后的标志 mark:" + atomicMarkableReference.isMarked()); boolean b1 = atomicMarkableReference.compareAndSet(2, 3, true, false); //是否更新成功false更新后的值:2更新后的标志 mark:false System.out.println("是否更新成功" + b1 + "更新后的值:" + atomicMarkableReference.getReference() + "更新后的标志 mark:" + atomicMarkableReference.isMarked()); }
总结:
1、操作系统层面的CAS是一条CPU原子指令(cmpxchg指令),由于该指令具有原子性,因此使用CAS不会造成数据不一致的问题。
2、使用CAS无锁编程步骤如下,获取字段中的期望值(旧值)与内存地址上的值作比较(没有修改之前的旧值),如果两个值相等,就把新值放在字段的内存地址上,失败则自旋。
3、并发线程越少,自旋越少CAS性能越高,反之并发线程越多,自旋的次数越多,CAS的性能就越低。
4、jdk中的原子类大都是使用CAS实现的。
5、解决ABA问题使用版本号,jdk中有两个类AtomicStampedReference和AtomicMarkableReference解决ABA的问题。
参考:
《JAVA高并发核心编程(卷2):多线程、锁、JMM、JUC、高并发设计》-尼恩编著