java原子类和CAS

来源:互联网 发布:2017乘联会销量数据 编辑:程序博客网 时间:2024/05/28 23:20

本文转载自:http://blog.csdn.net/jijianshuai/article/details/70853776

什么是CAS

    CAS的全称为Compare-And-Swap,直译就是对比交换。是一条CPU的原子指令,其作用是让CPU先进行比较两个值是否相等,然后原子地更新某个位置的值,经过调查发现,其实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。 
    简单解释:CAS操作需要输入两个数值,一个旧值(期望操作前的值)和一个新值,在操作期间先比较下在旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。 
    CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。 
    相信sql大家都熟悉,类似sql中的条件更新一样:update set id=3 from table where id=2。因为单条sql执行具有原子性,如果有多个线程同时执行此sql语句,只有一条能更新成功。

    如果不使用CAS,在高并发下,多线程同时修改一个变量的值我们需要synchronized加锁(可能有人说可以用Lock加锁,Lock底层的AQS也是基于CAS进行获取锁的)。

public class Test {    private int i=0;    public synchronized int add(){        return i++;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

    Java中为我们提供了AtomicInteger 原子类(底层基于CAS进行更新数据的),不需要加锁就在多线程并发场景下实现数据的一致性。

public class Test {    private  AtomicInteger i = new AtomicInteger(0);    public int add(){        return i.addAndGet(1);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

java.util.concurrent包都中的实现类都是基于volatile和CAS来实现的。尤其java.util.concurrent.atomic包下的原子类。

简单介绍下volatile特性: 
1. 内存可见性(当一个线程修改volatile变量的值时,另一个线程就可以实时看到此变量的更新值) 
2. 禁止指令重排(volatile变量之前的变量执行先于volatile变量执行,volatile之后的变量执行在volatile变量之后)

AtomicInteger 源码解析

public class AtomicInteger extends Number implements java.io.Serializable {    private static final Unsafe unsafe = Unsafe.getUnsafe();    private static final long valueOffset;    static {        try {            //用于获取value字段相对当前对象的“起始地址”的偏移量            valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }    private volatile int value;    //返回当前值    public final int get() {        return value;    }    //递增加detla    public final int getAndAdd(int delta) {        //三个参数,1、当前的实例 2、value实例变量的偏移量 3、当前value要加上的数(value+delta)。        return unsafe.getAndAddInt(this, valueOffset, delta);    }    //递增加1    public final int incrementAndGet() {        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;    }...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

我们可以看到 AtomicInteger 底层用的是volatile的变量和CAS来进行更改数据的。 
volatile保证线程的可见性,多线程并发时,一个线程修改数据,可以保证其它线程立马看到修改后的值 
CAS 保证数据更新的原子性。

Unsafe源码解析

下面分析下Unsafe 类中的实现。代码反编译出来的。

public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)  {    int i;    do      i = getIntVolatile(paramObject, paramLong);    while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));    return i;  }  public final long getAndAddLong(Object paramObject, long paramLong1, long paramLong2)  {    long l;    do      l = getLongVolatile(paramObject, paramLong1);    while (!compareAndSwapLong(paramObject, paramLong1, l, l + paramLong2));    return l;  }  public final int getAndSetInt(Object paramObject, long paramLong, int paramInt)  {    int i;    do      i = getIntVolatile(paramObject, paramLong);    while (!compareAndSwapInt(paramObject, paramLong, i, paramInt));    return i;  }  public final long getAndSetLong(Object paramObject, long paramLong1, long paramLong2)  {    long l;    do      l = getLongVolatile(paramObject, paramLong1);    while (!compareAndSwapLong(paramObject, paramLong1, l, paramLong2));    return l;  }  public final Object getAndSetObject(Object paramObject1, long paramLong, Object paramObject2)  {    Object localObject;    do      localObject = getObjectVolatile(paramObject1, paramLong);    while (!compareAndSwapObject(paramObject1, paramLong, localObject, paramObject2));    return localObject;  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)。

又从Unsafe类中发现,原子操作其实只支持下面三个方法。

  public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3);  public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2);  public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

我们发现Unsafe只提供了3种CAS方法:compareAndSwapObject、compareAndSwapInt和compareAndSwapLong。都是native方法。

AtomicBoolean 源码解析

public class AtomicBoolean implements java.io.Serializable {    private static final Unsafe unsafe = Unsafe.getUnsafe();    private static final long valueOffset;    static {        try {            valueOffset = unsafe.objectFieldOffset                (AtomicBoolean.class.getDeclaredField("value"));        } catch (Exception ex) { throw new Error(ex); }    }    private volatile int value;    public AtomicBoolean(boolean initialValue) {        value = initialValue ? 1 : 0;    }    public final boolean compareAndSet(boolean expect, boolean update) {        int e = expect ? 1 : 0;        int u = update ? 1 : 0;        return unsafe.compareAndSwapInt(this, valueOffset, e, u);    }    ...}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

从AtomicBoolean源码,发现他底层也是使用volatile类型的int 变量,跟AtomicInteger 实现方式一样,只不过是把Boolean转换成 0和1进行操作。

所以原子更新char、float和double变量也可以转换成int 或long来实现CAS的操作。

CAS缺点

  1. ABA问题。因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。 
    从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
  2. 循环时间长开销大。自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
原创粉丝点击