# 1.什么是CAS(Compare And Swap)

cas是一种基于锁的操作,而且是乐观锁。CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和A的值相等,那么就将内存里面的值更新成B。

public void withdraw(Integer amount) {
    while (true) {
    // 需要不断尝试,直到成功为止
        while (true) {
        // 比如拿到了旧值 1000
            int prev = balance.get();
        // 在这个基础上 1000-10 = 990
            int next = prev - amount;
        /*
        compareAndSet 正是做这个检查,在 set 前,先比较 prev 与当前值
        - 不一致了,next 作废,返回 false 表示失败
        比如,别的线程已经做了减法,当前值已经被减成了 990
        那么本线程的这次 990 就作废了,进入 while 下次循环重试
        - 一致,以 next 设置为新值,返回 true 表示成功
        */
            if (balance.compareAndSet(prev, next)) {
                break;
            }
        }
    }
}
  • 其实 CAS 的底层是 lock cmpxchg 指令(X86 架构),在单核 CPU 和多核 CPU 下都能够保证【比较-交换】的原子性。
  • 在多核状态下,某个核执行到带 lock 的指令时,CPU 会让总线锁住,当这个核把此指令执行完毕,再 开启总线。这个过程中不会被线程的调度机制所打断,保证了多个线程对内存操作的准确性,是原子的
  • CAS 必须借助 volatile 才能读取到共享变量的最新值来实现【比较并交换】的效果

# 2. 为什么无锁效率高

  • 无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而 synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
  • 但无锁情况下,因为线程要保持运行,需要额外 CPU 的支持,CPU 在这里就好比高速跑道,没有额外的跑道,线程想高速运行也无从谈起,虽然不会进入阻塞,但由于没有分到时间片,仍然会进入可运行状态,还 是会导致上下文切换。

# 3. CAS实现院子操作的三大问题

# 3.1 ABA问题

  • CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化则更新如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了 解决思路 在变量前⾯追加上版本号或者时间戳,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A

# 3.2 循环时间长开销大

  • 让JVM支持处理器提供的pause指令,pause指令能让自旋失败时cpu睡眠一小段时间在继续自旋,从而使得读操作的频率低很多,为解决内存冲突而导致的cpu流水线重排的代价也会小很多。

# 3.3 只能保证⼀个共享变量的原子操作

解决思路:1 使用锁。锁内的临界区代码可以保证只有当前线程操作。2 把多个共享变量合并成一个共享变量来操作。
从Java 1.5开始,JDK就提供了AtomicReference 类来保证引用对象之间的原⼦性,把多个变量放到⼀个对象⾥⾯进⾏CAS操作;