天天看点

JUC - 理解CAS机制

14.1 基本概念

CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并交换。

CAS机制当中使用了3个基本操作数:内存地址(initialValue),旧的预期值(expect),要修改的新值(update)。

更新一个变量的时候,只有当变量的预期值(expect)和内存地址(initialValue)当中的实际值相同时,才会将内存地址(initialValue)对应的值修改为(update)。

源码分析

/**
 * Atomically sets the value to the given updated value
 * if the current value {@code ==} the expected value.

 * @param expect the expected value
 * @param update the new value
 * @return {@code true} if successful. False return indicates that
 * the actual value was not equal to the expected value.
*/
// 参数: 期望值 更新值
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
           

代码示例

真实值和期望值相同,就修改成功,真实值和期望值不同,就修改失败!!!

package cn.guardwhy.cas01;

import java.util.concurrent.atomic.AtomicInteger;

/*
CAS : 比较并交换 compareAndSet
*/
public class CASDemo01 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2011);
        /*
          源码示例 参数: 期望值 更新值
          public final boolean compareAndSet(int expect, int update) {
                return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
            }
        */
        // 期望值是 2011 ,后来改为2021 所以结果为2021
        System.out.println(atomicInteger.compareAndSet(2011, 2021)); // true
        System.out.println(atomicInteger.get()); // 2021

        // 期望值是2011,后改为1024 所以最终结果为false 2021
        System.out.println(atomicInteger.compareAndSet(2011, 1024)); // false
        System.out.println(atomicInteger.get()); // 2021
    }
}
           

14.2 CAS底层原理

atomicInteger.getAndIncrement( ) ; 这里的自增 + 1 是如何实现的。

atomicInteger.getAndIncrement(); // 分析源码,如何实现的i++安全的问题
/**
 * Atomically increments by one the current value.
 *
 * @return the previous value
*/
public final int getAndIncrement() { 
    // this表示当前对象, valueOffset内存偏移量,内存地址,固定写死
    return unsafe.getAndAddInt(this, valueOffset, 1);
}
           

14.2.1 Unsafe类

UnSafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,UnSafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类存在于 sun.misc包中,其内部方法操作可以像C++的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。

注意:Unsafe类中的所有方法都是Native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务。

14.2.2 变量valueOffset

表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。

14.2.3 源码分析

JUC - 理解CAS机制

Compare-And-Swap是一条CPU并发原语,是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。CAS并发原语体现在JAVA语言中就是 sun.misc.Unsafe 类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

底层是自旋锁

public final int getAndAddInt(Object var1, long var2, int var4) {
     int var5;
     // 自旋锁
     do {
         // 获取传入对象的地址
         var5 = this.getIntVolatile(var1, var2);
         // 比较并且交换,如果var1, var2还是原来的var5,就执行内存偏移 + 1, var5 + var4操作
     } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

     return var5;
 }
           
JUC - 理解CAS机制

14.3 CAS总结

CAS(CompareAndSwap)

比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止(自旋锁)。

CAS 的缺点

1、循环会很耗时

  • 源码中存在 一个 do…while 操作,如果CAS失败就会一直进行尝试。

2、只能保证一个共享变量的原子操作。

  • 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。
  • 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候只能用锁来保证原子性。

14.4 原子引用

14.4.1 ABA问题(狸猫换太子)

CAS算法实现前提:需要取出内存中某时刻的数据并在当下时刻比较并交换,那么在这个时间差内会导致数据的变化。

案例分析

假设一个线程a从内存取出A (A=1) , 线程b也从内存中取出A (A=1) ,但是线程b速度比较快,把A进行了CAS(1, 3)操作,然后线程b又将内存A进行CAS(3,1)操作。此刻线程a 进行CAS操作发现内存中仍然是A,然后线程a操作成功。尽管线程a的CAS操作成功,但是这个过程是有问题的。

JUC - 理解CAS机制

代码示例

package cn.guardwhy.cas01;

import java.util.concurrent.atomic.AtomicInteger;

// CAS : 比较并交换 compareAndSet
public class CASDemo02 {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2011);
        /*
          期望、更新
          public final boolean compareAndSet(int expect, int update)
          如果期望值达到了,那么就更新,否则就不更新,CAS是CPU的并发原语
        */

        System.out.println("======捣乱的线程========");
        System.out.println(atomicInteger.compareAndSet(2011, 2021));    // true
        System.out.println(atomicInteger.get());    // 2021

        System.out.println(atomicInteger.compareAndSet(2021, 2011));    // true
        System.out.println(atomicInteger.get());    // 2011

        System.out.println("======期待的线程========");
        System.out.println(atomicInteger.compareAndSet(2011, 123)); // true
        System.out.println(atomicInteger.get());    // 123
    }
}
           

14.2 AtomicStampedReference

带版本号的原子操作,类似于乐观锁。

JUC - 理解CAS机制

源码分析

# 创建AtomicStampedReference对象
AtomicStampedReference<Integer> AtomicStampedReference = new AtomicStampedReference<>(1,1);

# 2.查看AtomicStampedReference源码
/**
 * Creates a new {@code AtomicStampedReference} with the given
 * initial values.
 *
 * @param initialRef the initial reference
 * @param initialStamp the initial stamp
*/
	
public AtomicStampedReference(V initialRef, int initialStamp) { 
	// initialValue:初始值 initialStamp:初始的时间戳
	pair = Pair.of(initialRef, initialStamp);
}
           

构造方法

JUC - 理解CAS机制

源码分析

/**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
*/
public boolean compareAndSet(V   expectedReference,
                             V   newReference,
                             int expectedStamp,	// 拿到最新的版本号
                             int newStamp) {	// 版本号+1操作(更新)
    Pair<V> current = pair;
    return
        expectedReference == current.reference &&
        expectedStamp == current.stamp &&
        ((newReference == current.reference &&
          newStamp == current.stamp) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
           

14.3 代码示例

package cn.guardwhy.cas01;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

/*
* 解决ABA问题
*/
public class ABADemo03 {
    public static void main(String[] args) {
        // 1. 正常业务操作时候,泛型类型一般都是user对象
        AtomicStampedReference<Integer> atomicStampedReference
                = new AtomicStampedReference<>(1,1); // 两个值: 初始值,初始版本号(初始的时间戳)

        // 2.1CAS:比较并交换
        new Thread(()->{
            // 2.2 获得最新的版本号
            int stamp = atomicStampedReference.getStamp();
            // 拿到最新版本
            System.out.println("a1==>" + stamp);

            try {
                // 2.3 休眠1s
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            atomicStampedReference.compareAndSet(1, 2,
                    // 拿到最新的版本号,版本号+1操作(更新)
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            // 拿到最新版本号
            System.out.println("a2==>" + atomicStampedReference.getStamp());

            System.out.println(atomicStampedReference.compareAndSet(2, 1,
                    // 拿到最新的版本号,版本号+1操作(更新)
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));


            // 拿到最新版本号
            System.out.println("a3==>" + atomicStampedReference.getStamp());

        },"a").start();

        new Thread(()->{
            // 获得最新版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("b1==>" + stamp);
            try {
                // 休眠2s
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicStampedReference.compareAndSet(1, 66,
                    stamp, stamp + 1));

            System.out.println("b2==>" + atomicStampedReference.getStamp());
        }, "b").start();
    }
}
           

执行结果

JUC - 理解CAS机制

继续阅读