概述

  • CAS:CompareAndSwap 比较并交换
  • 定义:CAS包含三个参数,分别是内存位置(V)、期望值(A)、更新值(B),也即是说内存位置的值和期望值是一致的,就是将值更新为更新值。 ```java public class RequestDemo { static int count = 0;

    public static void main(String[] args) throws Exception {

    1. CountDownLatch latch = new CountDownLatch(100);
    2. for (int i = 0; i < 100; i++) {
    3. new Thread(() -> {
    4. try {
    5. for (int i1 = 0; i1 < 10; i1++) {
    6. try {
    7. request();
    8. } catch (InterruptedException e) {
    9. e.printStackTrace();
    10. }
    11. }
    12. } finally {
    13. latch.countDown();
    14. }
    15. }, String.valueOf(i)).start();
    16. }
    17. latch.await();
    18. System.out.println(count);

    }

    public static synchronized boolean compareAndSwap(int expectCount, int newCount) {

    1. if (getCount() == expectCount) {
    2. count = newCount;
    3. return true;
    4. }
    5. return false;

    }

    public static int getCount() {

    1. return count;

    }

    public static void request() throws InterruptedException {

    1. Thread.sleep(5);
    2. int expectCount;
    3. while (!compareAndSwap(expectCount = getCount(), expectCount + 1)) {
    4. }

    } }

  1. <a name="rF84i"></a>
  2. # JDK提供的CAS
  3. ```java
  4. public final class Unsafe {
  5. // 参数1 表示要操作的对象
  6. // 参数2 表示要操作对象中属性地址的偏移量
  7. // 参数3 预期值
  8. // 参数4 需要更新的值
  9. public final native boolean compareAndSwapObject(Object o, long offset,
  10. Object expected,
  11. Object x);
  12. public final native boolean compareAndSwapInt(Object o, long offset,
  13. int expected,
  14. int x);
  15. public final native boolean compareAndSwapLong(Object o, long offset,
  16. long expected,
  17. long x);
  18. }

CAS的实现原理

  • CAS通过调用JNI实现,JNI:Java Native Interface 允许Java调用其他语言,而CompareAndSwapXxx系列的方法就是借助”C语言”来调用CPU底层指令实现的,以Intel x86来说,最终映射到CPU的指令就是”cmpxchg”,这是一个原子指令,实现并比较替换的操作。
  • cmpxchg 如何保证多核心下的线程安全:系统底层进行CAS操作的时候,会判断当前操作系统是否是多核心,如果是,就给”总线”加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行CAS操作,也就说CAS是平台级别的。

    CAS的问题和解决

  • CAS会存在ABA问题,也就是CAS在操作的时候会检查当前的值和期望的值是否是一样的,如果没有变化则更新,但是如果一个值原来是A,在CAS方法执行之前,被其他线程修改为了B,然后又修改成了A,那么这个时候看起来没有发生变化,CAS也是可以执行成功的,但是实际上这个值已经做了改变。

  • 如何解决ABA问题,为每个值增加一个唯一的版本号。
  • JDK提供了解决的方式 AtomicStampedReferenceAtomicMarkableReference

    1. public class AtomicStampedReference<V> {
    2. private static class Pair<T> {
    3. // 引用
    4. final T reference;
    5. // 版本戳
    6. final int stamp;
    7. private Pair(T reference, int stamp) {
    8. this.reference = reference;
    9. this.stamp = stamp;
    10. }
    11. static <T> Pair<T> of(T reference, int stamp) {
    12. return new Pair<T>(reference, stamp);
    13. }
    14. }
    15. // expectedReference 期望的引用
    16. // newReference 新的引用
    17. // expectedStamp 期望的版本号
    18. // newStamp 新的版本号
    19. public boolean compareAndSet(V expectedReference,
    20. V newReference,
    21. int expectedStamp,
    22. int newStamp) {
    23. Pair<V> current = pair;
    24. return
    25. expectedReference == current.reference &&
    26. expectedStamp == current.stamp &&
    27. ((newReference == current.reference &&
    28. newStamp == current.stamp) ||
    29. casPair(current, Pair.of(newReference, newStamp)));
    30. }
    31. // cas Pair
    32. // CAS 更新
    33. private boolean casPair(Pair<V> cmp, Pair<V> val) {
    34. return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
    35. }
    36. }

    ```java public class AbaDemo1 { private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) {

    1. new Thread(() -> {
    2. int expNum = atomicInteger.get();
    3. int newNum = expNum + 1;
    4. try {
    5. Thread.sleep(1000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. boolean res = atomicInteger.compareAndSet(expNum, newNum);
    10. System.out.println(Thread.currentThread().getName() + " " + res);
    11. }, "t1").start();
    12. new Thread(() -> {
    13. try {
    14. Thread.sleep(20);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. int incre = atomicInteger.incrementAndGet();
    19. int decre = atomicInteger.decrementAndGet();
    20. System.out.println(Thread.currentThread().getName() + " incre: " + incre + ",decre: " + decre);
    21. }, "t1").start();

    } }

  1. ```java
  2. /**
  3. * 版本号没有问题,但是人为的把版本号瞎搞,就有问题
  4. *
  5. * @author icanci
  6. * @since 1.0 Created in 2022/06/05 22:45
  7. */
  8. public class AbaDemo2 {
  9. private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1, 1);
  10. public static void main(String[] args) {
  11. new Thread(() -> {
  12. int oldReference = atomicStampedReference.getReference();
  13. int newReference = oldReference + 1;
  14. try {
  15. Thread.sleep(1000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. int stamp = atomicStampedReference.getStamp();
  20. int newStamp = stamp + 1;
  21. boolean res = atomicStampedReference.compareAndSet(oldReference, newReference, stamp, newStamp);
  22. System.out.println(Thread.currentThread().getName() + " " + res);
  23. }, "t1").start();
  24. new Thread(() -> {
  25. try {
  26. Thread.sleep(20);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. Integer reference = atomicStampedReference.getReference();
  31. int stamp = atomicStampedReference.getStamp();
  32. boolean b = atomicStampedReference.compareAndSet(reference, reference + 1, stamp, stamp + 1);
  33. boolean b1 = atomicStampedReference.compareAndSet(reference + 1, reference, stamp + 1, stamp);
  34. System.out.println(Thread.currentThread().getName() + " incre: " + b + ",decre: " + b1);
  35. }, "t1").start();
  36. }
  37. }
  1. public class AbaDemo3 {
  2. private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1, 1);
  3. public static void main(String[] args) {
  4. new Thread(() -> {
  5. int oldReference = atomicStampedReference.getReference();
  6. int newReference = oldReference + 1;
  7. try {
  8. Thread.sleep(1000);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. int stamp = atomicStampedReference.getStamp();
  13. int newStamp = stamp + 1;
  14. boolean res = atomicStampedReference.compareAndSet(oldReference, newReference, stamp, newStamp);
  15. System.out.println(Thread.currentThread().getName() + " " + res);
  16. }, "t1").start();
  17. new Thread(() -> {
  18. try {
  19. Thread.sleep(20);
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. Integer reference = atomicStampedReference.getReference();
  24. int stamp = atomicStampedReference.getStamp();
  25. boolean b = atomicStampedReference.compareAndSet(reference, reference + 1, stamp, stamp + 1);
  26. boolean b1 = atomicStampedReference.compareAndSet(reference + 1, reference, stamp + 1, stamp + 1 + 1);
  27. System.out.println(Thread.currentThread().getName() + " incre: " + b + ",decre: " + b1);
  28. }, "t1").start();
  29. }
  30. }