Contents

Java并发编程——CAS

CAS是什么?

  • compare and swap
  • 本质是一条CPU的原子指令,可以保证共享变量修改的原子性。
执行函数:CAS(V,E,N)
  • 当且仅当内存地址V中的值等于 预期值E 时,将内存V中的值改为N,否则会进行自旋操作(一般情况 下),即不断的重试。

Java中对CAS的实现

  • Java不能像C/C++那样直接操作内存区域,需要通过本地方法(native方法)来访问。JAVA中的CAS操作都 是通过sun包下Unsafe类实(sun.misc.Unsafe),而Unsafe类中的方法都是native方法。
//offset:内存偏移量,offset 为o对象所属类中,某个属性在类中的内存地址偏移量
public final native boolean compareAndSwapInt(Object o,long offset,int expected,int x);
  • compareAndSwapInt在看openJDK8的源码中位置:
    • openjdk8/hotspot/src/share/vm/prims/unsafe.cpp

CAS缺陷

  1. 自旋开销(循环时间太长)
    • 原子类AtomicInteger#getAndIncrement()的方法
  2. 只能保证一个共享变量的原子操作
  3. ABA问题
    • 如果一个值原来是A,变成了B,然后又变成了A,那么在CAS检查的时候会发现没有改变,但 是实质上它已经发生了改变,这就是所谓的ABA问题
    • 解决方案:加版本号(类似于乐观锁)。即 在每个变量绑定一个版本号,每次改变时加1,即A —> B —> A,变成1A —> 2B —> 3A。
      • 例如为了消除原子类AtomicInteger ar = new AtomicInteger(100);的ABA问题, 可以改用AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(100, 1);

原子变量类

  • java.util.concurrent.atomic
    • 用法简单、性能高效、线程安全地更新一个变量的方式
    • 可以解决volatile原子性操作变量的问题
    • Atomic包里的类基本都是使用Unsafe实现的包装类

Atomic常用类

  • 基本类型
    • AtomicInteger
    • AtomicLong
    • AtomicBoolean
  • 引用类型
    • AtomicReference
    • AtomicStampedReference
    • AtomicMarkableReference
  • 数组类型
    • AtomicIntegerArray
    • AtomicLongArray
    • AtomicReferenceArray
  • 对象的属性修改类型
    • AtomicIntegerFieldUpdater
    • AtomicLongFieldUpdater
    • AtomicReferenceFieldUpdater

AtomicInteger主要API

  • 1
    • get():获取当前值
    • getAndAdd(int delta):获取当前值,并加上预期的值
    • getAndSet(int newValue):获取当前值,并设置新值
    • getAndIncrement():获取当前值,并自增
    • getAndDecrement():获取当前值,并自减
  • 2
    • addAndGet(int delta):加上预期的值,返回增加后的数据
    • incrementAndGet():增加1,返回增加后的值
    • decrementAndGet():减少1,返回减少后的值
    • lazySet(int newValue):仅仅当get时才会set
  • 3 compareAndSet(int expect, int update):尝试新增后对比,若增加成功则返回true否则返回false

demo

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        VolatileDemo demo = new VolatileDemo();
        for (int i = 0; i < 2; i++) {
            Thread t = new Thread(demo);
            t.start();
        }
        Thread.sleep(1000);
        System.out.println("count = " + demo.count);
    }

    static class VolatileDemo implements Runnable {
        public AtomicInteger count = new AtomicInteger(0);

        public void run() {
            addCount();
        }

        public void addCount() {
            for (int i = 0; i < 10000; i++) {
                count.incrementAndGet();
            }
        }
    }
}