java多线程中的CAS

CAS简介

CAS是Compare And Swap三个单词的首字母(中文是比较并交换),CAS 的基本操作:线程在把数据更新到主内存时,会再次读取主内存变量的值,如果这次读取变量的值与期望的值(操作起始时读取的值)一样就更新。

上图中t1线程和t2线程要修改同一个变量num,对其进行加1操作,两个线程都将num读取到了自己的内存中进行修改,假设t1先修改完毕之后,会再次读取主内存num的值,此时读到6,该值跟修改之前是一样的,所以t1成功的将修改之后的值7写入到主内存中。t2线程修改完毕之后,也会再次读取主内存num的值,此时读到7,跟修改之前的值不一样,所以t2不能修改主内存的值,t2可以放弃修改或者将现在num的值7做加1操作之后再次重复上面的操作,这个就是CAS的操作。

下面通过CAS的方式实现一个线程安全的计数器,一定要注意在将线程工作内存中的数据写入到主内存之前做一个判断。

/*
    使用CAS实现计数器
 */
public class Counter {
    
    //保证内存可见性
    private volatile int value;
    
    /*
        将工作内存中之前的值和工作内存中的值进行比较
     */
    public boolean compareAndSwap(int exceptedValue, int newValue) {
        synchronized (this) {
            if (value == exceptedValue) {
                value = newValue;//修改主内存中的value
                return true;
            }
            return false;
        }
    }
    
    /*
        修改并获取值
     */
    public int incrementAndGet() {
        int oldValue;
        int newValue;
        do {
            oldValue = value;
            //修改线程工作内存中的值
            newValue = oldValue + 1;
        } while (!compareAndSwap(oldValue, newValue));

        return value;
    }

}

创建测试类,模拟多个线程,可以看到打印结果是从1至100

Counter counter = new Counter();

//创建多个线程
for (int i = 0; i < 100 ; i++) {
    new Thread(()->{
        System.out.println(counter.incrementAndGet());
    }).start();

}

CAS的ABA问题

有一个共享变量的值是8,在实际应用中可能会出现下面的情况:

  1. 线程1将变量的值改成6
  2. 线程2将变量的值改成8

当线程3看到变量的值是8,那现在是否认为变量的值没有被其他线程所修改呢。这个就是CAS的ABA问题,即变量的值经历了A–>B–>A的变化。

要想解决ABA问题的话,可以引入一个版本号,每次修改共享变量的时候,版本号加1,通过版本号就可以得知变量是否有被修改。

[A,1]–>[B,2]–>[C,3]

在jdk中提供了AtomicStampedReference类就是基于该思想实现的。

CAS的原理

CAS 的底层是 lock cmpxchg 指令保证了操作的原子性,在多CPU下,某个CPU执行 lock 的指令时,通过总线锁让其他CPU的请求阻塞,指令执行结束之后再开启总线锁,该过程中不会被打断,从而保证了多个线程对内存操作的原子性。

CAS的特点

CAS 是基于乐观锁的思想,线程不会阻塞,可以实现无锁并发,适用于线程数少,多核 CPU 的场景下,倘若线程数较多的话,会发生多次重试(相当于是多次循环),会耗费CPU资源。

原子类

jdk中提供了一些原子类,这些原子类是基于CAS来实现的,里面都使用了sun.misc.Unsafe类,Unsafe类提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率,倘若对Unsafe不是特别熟悉的话,直接使用会带来一些问题,这也是Unsafe类名的原因。

jdk提供的一些原子类:

  1. 基本数据
    1. AtomicBoolean
    2. AtomicInteger
    3. AtomicLong
  2. 引用数据
    1. AtomicReference
    2. AtomicStampedReference
    3. AtomicMarkableReference
  3. 数组
    1. AtomicIntegerArray
    2. AtomicLongArray
    3. AtomicReferenceArray
  4. 字段更新器
    1. AtomicIntegerFieldUpdater
    2. AtomicLongFieldUpdater
    3. AtomicReferenceFieldUpdater