关于CAS

#概念以及特性

CAS (Compare and Swap) :比较并交换。

CAS的算法过程:CAS(V, E, N) 包含3个参数,其中V表示要更新的变量(内存值),E表示期望值(旧值),N表示新值。当且仅当V值与E值相等时,才会将V值设置为N;如果V值和E值不等,说明已有其他线程对V值做了更新,则当前线程不会进行任何操作,CAS返回V的真实值。

CAS是一种乐观锁策略,即认为可以成功完成操作,而不会遇到并发的情况。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新。失败的线程不会被挂起而进入阻塞状态,而是允许继续尝试。

#CAS存在的问题

#ABA问题

当一个线程读取内存变量V的值为A,此时另一个线程也读取到内存变量V的值为A并且进行了操作将值A更改为B后续有又更改回A,此时线程1对变量V进行CAS操作发现变量V的值仍然是A进而操作成功。尽管线程1操作成功,但变量V的值发生了变化。这就是ABA问题。

JUC包中Atomic类下的AtomicStampedReference子类通过控制变量V的版本号来解决ABA问题。每次对变量进行数据修改操作时都会带上一个版本号,一旦数据修改操作的版本号和数据自身的版本号一致时就可以执行修改操作并对数据自身版本号执行+1操作,否则执行失败。

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

/**
 * @author Chiaki
 */
public class ABADemo {
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) throws InterruptedException {
        System.out.println("========ABA问题产生========");
        new Thread(() -> {
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "Thread01").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2020) + " " + atomicReference.get());
        }, "Thread02").start();
        TimeUnit.SECONDS.sleep(2);

        System.out.println("========ABA问题解决========");

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "线程第2次版本号:" + atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "线程第3次版本号:" + atomicStampedReference.getStamp());
        }, "Thread03").start();

        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "线程第1次版本号:" + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean res = atomicStampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "是否修改成功:" + res);
            System.out.println(Thread.currentThread().getName() + "当前版本号为:" + atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName() + "当前最新值为:" + atomicStampedReference.getReference());
        }, "Thread04").start();
    }
}

执行结果:

about-cas.png

#自旋(不断尝试)会浪费CPU资源

CAS不会造成阻塞,操作失败后会重新尝试,重试是通过自旋实现的,自旋操作会浪费大量CPU资源。

解决思路:采用自适应自旋甚至是采用synchronized

#无法实现公平性