CAS (Compare And Swap)
比较并交换html
CAS 是一条CPU并发原语。功能是判断内存某个位置的值是否为预期值,若是是则更改成最新值,这个过程是原子的。CAS并发原语体如今Java语言中就是sun.misc.Unsafe类中的各个本地方法。这是一种彻底依赖于硬件
的功能,经过它实现了原子操做。原语的执行时连续的,在执行过程当中不容许被中断,也就是说CAS是一条CPU的原子指令,不会形成数据不一致。java
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。代码输出结果以下:post
true current data : 10
false current data : 10
复制代码
概述一下,CAS涉及到3个操做数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值V修改成B,不然什么都不作。this
咱们知道在多线程状况下i++
操做其实包含了三个步骤,是线程不安全的。咱们可使用atomicInteger.getAndIncrement()
实现线程安全的i++
,下面经过看下该方法的实现了解如何作到线程安全。atom
/** * 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类。spa
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,不然什么都不作。 这时再去看开篇的那句话是否就理解了呢。
getAndAddInt
方法中的第一个参数是object,即this 表示当前对象,从数量上来讲这里只能控制一个共享变量。而synchronized加锁能锁住一段代码,能够保证多个线程都被锁住。尽管线程A的CAS操做成功,可是不表明这个过程就没有问题。