1. AtomicInteger

CAS: 比较与交换
当前工作内存快照的值和主内存的值进行比较,相同才设置。

  1. package com.interview.demo;
  2. import java.util.concurrent.atomic.AtomicInteger;
  3. /**
  4. * @Author leijs
  5. * @date 2022/3/28
  6. */
  7. public class CASDemo {
  8. public static void main(String[] args) {
  9. AtomicInteger atomicInteger = new AtomicInteger(5);
  10. atomicInteger.compareAndSet(5, 7);
  11. System.out.println(atomicInteger.get()); // 7
  12. atomicInteger.compareAndSet(5, 9); // false
  13. System.out.println(atomicInteger.get()); // 7
  14. }
  15. }

2. 底层原理

  • 自旋锁
  • Unsafe类

1648440192(1).png

Unsafe

是cas的核心类,由于java方法无法直接访问底层系统,需要通过本地native方法访问,unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中的CAS操作的执行依赖Unsafe类的方法。
注意Unsafe类的所有方法都是native方法修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务。

变量valueOffset

表示该变量在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
1648439131(1).png

变量value

value用volatile修饰,保证了多线程之间的内存可见性。

3. CAS 是什么?

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

底层源码

1648441189(1).png1648441210(1).png
核心do…while….
var1: AtomicInteger对象本身
var2: 该对象的引用地址
var4:需要变动的数值
var5:是通过var1 和var2 找出的主内存的真实值
用该对象当前的值和var5比较,如果相同,更新var5+var4并且返回true,如果不同,继续取值然后再比较,直到更新完成。

4. CAS的缺点

  • 如果CAS失败,会一直进行尝试,CPU长时间一直不成功,可能会给CPU带来很大的开销(自旋)

    循环时间长,开销大

  • 只能保证一个共享变量的原子操作

    对于多个共享变量的操作,循环CAS就无法保证操作的原子性,这个时候只能用锁来保证原子性。

  • 可能存在ABA问题

    5. ABA问题

    ABA出现原因

    ABA -> 原子引用更新 -> 怎么解决?
    CAS算法一个重要的前提是需要取出内存中某个时刻的数据并在当下时刻比较与交换,那么这个时间差会导致数据的变化。比方说一个线程one从内存位置V取出数据A, 这个时候另外一个线程也从内存中取出A,并且线程two进行一些操作将值改成了B,然后线程two又将位置V的数据变成A,这个时候线程one操作发现内存中仍然是A,然后线程one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程是没有问题的。
    ABA只管结果,不管过程。

    原子引用

    ```java package com.interview.demo;

import com.interview.demo.entity.User;

import java.util.concurrent.atomic.AtomicReference;

/**

  • @Author leijs
  • @date 2022/3/28 */ public class AtomicReferenceDemo { public static void main(String[] args) {

    1. User zhangsan = new User("zhangsan", 29);
    2. User lisi = new User("zhangsan", 16);
    3. AtomicReference<User> atomicReference = new AtomicReference<>();
    4. atomicReference.set(zhangsan);
    5. atomicReference.compareAndSet(zhangsan, lisi);
    6. System.out.println(atomicReference.get().getAge());

    } }

  1. <a name="l687U"></a>
  2. ### 时间戳原子引用
  3. 增加一种机制,那就是修改版本号(类时间戳)<br />类似乐观锁。
  4. ```java
  5. package com.interview.demo;
  6. import java.util.concurrent.TimeUnit;
  7. import java.util.concurrent.atomic.AtomicReference;
  8. import java.util.concurrent.atomic.AtomicStampedReference;
  9. /**
  10. * @Author leijs
  11. * @date 2022/3/28
  12. */
  13. public class ABADemo {
  14. static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
  15. static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(100, 1);
  16. public static void main(String[] args) {
  17. new Thread(() -> {
  18. atomicReference.compareAndSet(100, 101);
  19. atomicReference.compareAndSet(101, 100);
  20. }, "t1").start();
  21. new Thread(() -> {
  22. try {
  23. TimeUnit.SECONDS.sleep(1);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println(Thread.currentThread().getName() + ", " + atomicReference.compareAndSet(100, 2022)
  28. + "," + atomicReference.get());
  29. atomicReference.compareAndSet(100, 2022);
  30. }, "t2").start();
  31. // ABA 解决
  32. new Thread(() -> {
  33. int timestamp = atomicStampedReference.getStamp();
  34. try {
  35. TimeUnit.SECONDS.sleep(1);
  36. } catch (InterruptedException e) {
  37. e.printStackTrace();
  38. }
  39. System.out.println(Thread.currentThread().getName() + ", " +
  40. atomicStampedReference.compareAndSet(100, 101,
  41. timestamp, timestamp + 1)
  42. + "," + atomicStampedReference.getStamp());
  43. System.out.println(Thread.currentThread().getName() + ", " +
  44. atomicStampedReference.compareAndSet(101, 100,
  45. atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)
  46. + "," + atomicStampedReference.getStamp());
  47. }, "t3").start();
  48. new Thread(() -> {
  49. int timestamp = atomicStampedReference.getStamp();
  50. try {
  51. TimeUnit.SECONDS.sleep(5);
  52. } catch (InterruptedException e) {
  53. e.printStackTrace();
  54. }
  55. System.out.println(Thread.currentThread().getName() + ", " +
  56. atomicStampedReference.compareAndSet(100, 2023, timestamp, timestamp + 1)
  57. + "," + atomicStampedReference.getStamp() + "," + atomicStampedReference.getReference());
  58. }, "t4").start();
  59. }
  60. }