java原子类和CAS

CAS: Compare-And-Swap, 比较交换。

CAS操作需要三个参数: CAS(V,E,N)

V: 内存值

E:旧的预期值,一个线程间共享的变量,首先主存中会保留一份,然后每个线程的工作内存中也会保留一份副本,即预期值。

N:新值

只有当 V=E的时候,才会将V设为N。

CAS操作是原子性的,所以多线程并发使用CAS更新数据时,可以不使用锁。JDK中大量使用了CAS来更新数据而防止加锁(synchronized 重量级锁)来保持原子更新。 

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

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


无锁的线程安全整数:  AtomicInteger

可以把AtomicInteger看作是一个整数,它是可变的,并且是线程安全的。对其进行修改等任何操作,都是用CAS指令进行的。

内部实现:

核心字段:

private volatile int value;

value字段在AtomicInteger对象中的偏移量:

private static final long valueOffset;

我们先看下 incrementAndGet方法的内部实现:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

// 1、this:当前的实例 2、valueOffset:value实例变量的偏移量 3、当前value要加上的数(value+delta)。

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;
    }
...
}
volatile保证线程的可见性,多线程并发时,一个线程修改数据,可以保证其它线程立马看到修改后的值 

CAS 保证数据更新的原子性。

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;
  }  ........

从源码中发现,内部使用自旋的方式进行CAS更新(while循环进行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带来非常大的执行开销。


猜你喜欢

转载自blog.csdn.net/u012156116/article/details/80627909