java中原子类与CAS原理

在java.util.concurrent.atomic包中提供了很多原子类,包括三个原子更新基本类型:AtomicBoolean,AtomicInteger,AtomicLong;原子更新数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray;原子更新引用类型:AtomicReferenceFieldUpdater,AtomicMarkableReference,AtomicReference;原子更新字段类:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicStampedReference。

原子类的更新原理是一样的。这里我们不再详细得到介绍每一种原子更新类的使用,而是以AtomicInteger讲解原子更新基本类型的使用方式,如下为AtomicInteger提供的方法:

方法名称

方法介绍

int addAndGet(int delta)

以原子方式将输入的数值与实例中的值(AtomicInteger里的

value)相加,并返回结果。

boolean compareAndSet(int expect,int update)

如果输入的数值等于预期值,则以原子方

式将该值设置为输入的值。

int getAndIncrement()

以原子方式将当前值加1,注意,这里返回的是自增前的值。

void lazySet(int newValue)

最终会设置成newValue,使用lazySet设置值后,可能导致其他

线程在之后的一小段时间内还是可以读到旧的值

int getAndSet(int newValue)

以原子方式设置为newValue的值,并返回旧值

如下代码为AtomicInteger的一个使用示例,其他原子操作类的使用方式基本类似,在使用示例之后我们会介绍原子操作的基本原理。

public class AtomicIntegerDemo {
    static AtomicInteger ai = new AtomicInteger(1);
    public static void main(String[] args) {
        System.out.println(ai.getAndIncrement());
        System.out.println(ai.get());
    }
}

下面我们介绍 AtomicInteger的更新原理,AtomicInteger是如何进项原子更新的,首先我们查看getAndIncrement方法如下:

public final int getAndIncrement() {
    //循环
    for (;;) {
        int current = get();
        int next = current + 1;
        //比较并且设置值
        if (compareAndSet(current, next))
        return current;
    }
}
public final boolean compareAndSet(int expect, int update) {
    //只有期待的值和更新的值一样才会更新
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

在上面的代码中我们又看到了compareAndSwap,在并发包内,这是一个使用率非常高的词,也就是我们所说的CAS操作。在CAS操作中,for循环体的第一步先取得AtomicInteger里存储的数值,第二步对AtomicInteger的当前数值进行加1操作,关键的第三步调用compareAndSet方法来进行原子更新操作,该方法先检查当前数值是否等于current,等于意味着AtomicInteger的值没有被其他线程修改过,则将AtomicInteger的当前数值更新成next的值,如果不等compareAndSet方法会返回false,程序会进
入for循环重新进行compareAndSet操作。这就是CAS的基本原理,下面我们详细的介绍CAS的使用,以及CAS在其他并发包中的使用。

CAS(Compare And Swap)比较并且交换,CAS算法中包含三个参数Cas(V, E,N);V表示要更新的变量,E表示预期值,N表示新值。在当且仅当V值等于E值时,才会将V值设置为N,如果V值不等于E值,什么操作都不做直接返回。比如上面的代码,只有valueOffset与expect相等时,才会将valueOffset设置为update。其实CAS是采取乐观锁的思想,总是认为自己可以完成操作,这里就引入了自旋的操作,也就是上面代码中的for(;;),如果设置失败,将会一直自旋,直到设置成功为止。JVM中的CAS操作正是利用了处理器提供的CMPXCHG指令实现的。自旋CAS实现的基本思路就是循环进行CAS操作直到成功为止。如下为java提供的三个CAS方法:

public final native boolean compareAndSwapObject(Object o,long offset,Object expected,Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected, long x);

虽然CAS采取了无锁的方式保证了原子操作,但是也产生了一个新的问题也就是ABA问题。因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新。因此如果有两个线程,第一个线程从内存取出的值为A,第二个线程取出的也是A,如果它先更改为变成了B,再更改为A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是ABA问题。

ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。从Java 1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

猜你喜欢

转载自blog.csdn.net/wk19920726/article/details/108433671