CAS (Compare And Swap)
比较并交换
CAS 是一条CPU并发原语。功能是判断内存某个位置的值是否为预期值,如果是则更改为最新值,这个过程是原子的。CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个本地方法。这是一种完全依赖于 硬件
的功能,通过它实现了原子操作。原语的执行时连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成数据不一致。
CAS的作用是比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。 如果单看这句话不知道在说什么,那么就继续往下看,通篇结束之后再看读一下这句话吧。
这一篇通过AtomicInteger中两个方法来探索CAS到底是如何实现的。
先来看三行代码
AtomicInteger atomicInteger = new AtomicInteger(5); System.out.println(atomicInteger.compareAndSet(5,10)+ "/t current data : " +atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(5,9)+ "/t current data : " +atomicInteger.get()); 复制代码
上篇文章对volatile的理解 提到了,可以使用原子类AtomicInteger 解决原子性问题。这里用到了AtomicInteger.compareAndSet方法,两个参数分别是 期望值
和 更新值
,如果 期望值
与此时内存中的值相同,则将内存中的值更新为 更新值
,方法返回值为布尔类型,表示是否更新成功。以上三行代码,初始化时设置内存中值为5,第二行期望值与更新值分别为5和10,如果期望值5与内存中的值5相同,则将内存中的值更新为10,第二行返回true,当前内存中的值为10;第三行期望值为5,当前内存中的值为10,不符合期望结果,则不更新内存中的结果,返回false,当前内存中的值未更改,还是10。代码输出结果如下:
true current data : 10 false current data : 10 复制代码
概述一下,CAS涉及到3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
我们知道在多线程情况下 i++
操作其实包含了三个步骤,是线程不安全的。我们可以使用 atomicInteger.getAndIncrement()
实现线程安全的 i++
,下面通过看下该方法的实现了解如何做到线程安全。
/** * Atomically increments by one the current value. * * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } 复制代码
以上是getAndIncrement的源代码,注释在当前值的基础上原子增加一。实现为调用 unsafe
类的 getAndAddInt
方法,该方法有三个参数,分别是当前对象、内存偏移量、1,再往下看,就进入到unsafe类。
private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; 复制代码
了解这几个概念,我们继续往下看, getAndAddInt
是如何实现的:
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } 复制代码
这仍然是存在于unfase的方法,var1对应this,即当前对象,var2为内存偏移量,var4即是要添加的“1”。
当前方法为do-while结构,根据方法名中 getAndAdd
,首先 get
操作,do结构首先根据对象与内存地址获取当前值var5,从物理主内存中拷贝到自己的线程工作内存;然后关键点来了:while里面判断如果这一时刻内存地址中的值与var5相同,则修改为var5+var4,即 Add
成功,这时返回true,while取反,条件不成立,不再执行do;如果刚拿到var5的值,主物理内存中的值就发生变化了,即主内存与var5的值不一致,则不修改,返回false,while条件满足,继续执行do操作,再次获取当前内存中最新的值,继续判断,直至保证加一时拿到的结果时最新的。
以上操作简单说,就是拿到var5,然后对var5+1,担心加一操作时var5的值已经发生变化,所以加一时要再验证一遍,如果这个内存中的值还是var5那么就放心的加一操作,否则就不停的去获取最新的值。
下面举个例子来具体解释上一段内容:
假设线程A和线程B两个线程同时执行getAndAddInt操作:
getIntVolatile(var1,var2)
拿到value值为3,这是A被挂起。 getIntVolatile(var1,var2)
拿到value值为3,此时刚好相线程B没有被挂起并执行 compareAndSwapInt
方法比较内存值也是3,成功修改内存值为4,线程B完成操作。 compareAndSwapInt
方法,发现自己工作内存中的数字3与主内存中的数字4不一致,说明该值已经被其他线程抢先修改过了,那么A线程本次修改失败, 重新读取最新值重来一遍
,即重新执行do操作。 compareAndSwapInt
进行比较替换,直至成功。 再概述一下,CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。 这时再去看开篇的那句话是否就理解了呢。