Administrator
Published on 2021-06-30 / 243 Visits
0

【线程与并发】CAS

CAS

概念

  • CAS是 Compare And Swap(比较并交换)/ Compare And Exchange(比较并交换)/ Compare And Set (比较并设定)
  • 一种无锁算法/机制

无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。这里的非堵塞具体指的是线程执行中的某一步一直在做自旋操作。

  • java里的Atomic开头的类,都是用CAS来保证线程安全的,如AtomicInteger等。
  • 在不通过锁的情况下,怎么保证多个线程对同个数据同时修改的安全(一致性)?
    CAS:我们可以这样想,当一个线程在修改赋值的时候,会在操作时进行判断,拿到的值跟原来的值是否一样,不一样说明这个值被其他线程改了,得重新进行CAS操作(自旋的概念),如果一样说明没被改过,可以进行赋值;那如果一样,但在写的同时又被其他线程改了呢?这样怎么确保?实际上CPU里对应的有原语支持,就是在写的时候不能被其他线程写,即CAS操作时,指令不能被插入。

CAS在底层上,有汇编语言(操作系统硬件)上的支持,叫做cmpxchg。如果程序是在多处理器上运行,那前面还有LOCK_IF_MP的语句,最终就是lock cmpxchg,cmpxchg是不能保证原子性的,靠前面的lock语句硬件级别上保证,作用是在修改时不允许其他CPU打断/写入,lock是锁定北桥芯片的电信号。
synchronized和volotile底层上,都跟lock cmpxchg有关系。

微信图片_20210628221819.png

  • ABA问题,上面说了,在赋值的时候,会判断进行操作时,拿到的值跟原来的值是否一样,那假如有条线程改了把A改成B,然后又改成A,那这样我们判断时,表层上这个值没变化,但实际上又变化了。
    这种对基本数据类型的结果不影响,但是解决方法也有,我们可以通过对值加版本号,当改变时版本+1即可,后面CAS操作时,可以进行再加一步版本号的判断;此外,也可以通过加布尔类型来判断有没有改过。
    对引用数据类型可能就有问题,假如线程改了o = new Oject()里的某个成员变量或者重新指向o = new Oject(),但是CAS时判断的还是原来的o,但里面的值可能就被其他线程改了。

ABA问题的处理在JDK中,具体有类实现:
AtomicReference(无处理)和AtomicStampedReference(处理ABA)

  • 扩展:unsafe类,直接操控jvm里的内存,具备类似c/c++里的部分功能。
  • 原子操作的概念

"原子"代表最小的单位,所以原子操作可以看做最小的执行单位,该操作在执行完毕前不会被任何其他任务或事件打断。