一、CAS简介

CAS(Compare And Swap)是由硬件实现的.CAS 可以将read- modify - write 这类的操作转换为原子操作.i++自增操作包括三个子操作:

  • 从主内存读取i 变量值
  • 对i 的值加1
  • 再把加1 之后的值保存到主内存

CAS 原理: 在把数据更新到主内存时,再次读取主内存变量的值,如果现在变量的值与期望的值(操作起始时读取的值)一样就更新.

 

 

使用CAS实现线程安全的计数器

public class CASTest {
    public static void main(String[] args) {
        CASCounter casCounter = new CASCounter();
        for (int i = 0; i < 100000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(casCounter.incrementAndGet());
                }
            }).start();
        }
    }
}

class CASCounter {
    // 使用volatile 修饰value 值,使线程可见
    volatile private long value;

    public long getValue() {
        return value;
    }

    // 定义comare and swap 方法
    private boolean compareAndSwap(long expectedValue, long newValue) {
        // 如果当前value 的值与期望的expectedVAlue 值一样,就把当前的Value 字段替换为newValue 值
        synchronized (this) {
            if (value == expectedValue) {
                value = newValue;
                return true;
            } else {
                return false;
            }
        }
    }

    // 定义自增的方法
    public long incrementAndGet() {
        long oldvalue;
        long newValue;
        do {
            oldvalue = value;
            newValue = oldvalue + 1;
        } while (!compareAndSwap(oldvalue, newValue));
        return newValue;
    }
}

CAS 实现原子操作背后有一个假设: 共享变量的当前值与当前线程提供的期望值相同, 就认为这个变量没有被其他线程修改过.
实际上这种假设不一定总是成立.如有共享变量count = 0,
A 线程对count 值修改为10
B 线程对count 值修改为20
C 线程对count 值修改为0
当前线程看到count 变量的值现在是0,现在是否认为count 变量的值没有被其他线程更新呢? 这种结果是否能够接受??
这就是CAS 中的ABA 问题,即共享变量经历了A->B->A 的更新.是否能够接收ABA 问题跟实现的算法有关.如果想要规避ABA 问题,可以为共享变量引入一个修订号(时间戳), 每次修改共享变量时,相应的修订号就会增加1. ABA 变量更新过程变量: [A,0] ->[B,1]->[A,2], 每次对共享变量的修改都会导致修订号的增加,通过修订号依然可以准确判断变量是否被其他线程修改过. AtomicStampedReference 类就是基于这种思想产生的.

二、原子变量类

原子变量类基于CAS 实现的, 当对共享变量进行read-modify-write更新操作时,通过原子变量类可以保障操作的原子性与可见性.对变量的read-modify-write 更新操作是指当前操作不是一个简单的赋值,而是变量的新值依赖变量的旧值,如自增操作i++. 由于volatile 只能保证可见性,无法保障原子性, 原子变量类内部就是借助一个Volatile 变量,
并且保障了该变量的read-modify-write 操作的原子性, 有时把原子变量类看作增强的volatile 变量. 原子变量类有12 个,如:

分组 原子变量类
基础数据型 AtomicInteger, AtomicLong, AtomicBoolean
数组型

AtomicIntegerArray,
AtomicLongArray,AtomicReferenceArray

字段更新器 AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,

AtomicReferenceFieldUpdater

引用型

AtomicReference, AtomicStampedReference,
AtomicMarkableReference

 三、原子数组AtomicIntegerArray

public static void main(String[] args) {
        // 1)创建一个指定长度的原子数组
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
        System.out.println(atomicIntegerArray); // [0, 0, 0, 0, 0, 0, 0, 0, 0,
                                                // 0]
        // 2)返回指定位置的元素
        System.out.println(atomicIntegerArray.get(0)); // 0
        System.out.println(atomicIntegerArray.get(1)); // 0
        // 3)设置指定位置的元素
        atomicIntegerArray.set(0, 10);
        // 在设置数组元素的新值时, 同时返回数组元素的旧值
        System.out.println(atomicIntegerArray.getAndSet(1, 11)); // 0
        System.out.println(atomicIntegerArray); // [10, 11, 0, 0, 0, 0, 0, 0, 0,
                                                // 0]
        // 4)修改数组元素的值,把数组元素加上某个值
        System.out.println(atomicIntegerArray.addAndGet(0, 22)); // 32
        System.out.println(atomicIntegerArray.getAndAdd(1, 33)); // 11
        System.out.println(atomicIntegerArray); // [32, 44, 0, 0, 0, 0, 0, 0, 0,
                                                // 0]
        // 5)CAS 操作
        // 如果数组中索引值为0 的元素的值是32 , 就修改为222
        System.out.println(atomicIntegerArray.compareAndSet(0, 32, 222)); // true
        System.out.println(atomicIntegerArray); // [222, 44, 0, 0, 0, 0, 0, 0,
                                                // 0, 0]
        System.out.println(atomicIntegerArray.compareAndSet(1, 11, 333)); // false
        System.out.println(atomicIntegerArray);
        // 6)自增/自减
        System.out.println(atomicIntegerArray.incrementAndGet(0)); // 223, 相当于前缀
        System.out.println(atomicIntegerArray.getAndIncrement(1)); // 44, 相当于后缀
        System.out.println(atomicIntegerArray); // [223, 45, 0, 0, 0, 0, 0, 0,
                                                // 0, 0]
        System.out.println(atomicIntegerArray.decrementAndGet(2)); // -1
        System.out.println(atomicIntegerArray); // [223, 45, -1, 0, 0, 0, 0, 0,
                                                // 0, 0]
        System.out.println(atomicIntegerArray.getAndDecrement(3)); // 0
        System.out.println(atomicIntegerArray); // [223, 45, -1, -1, 0, 0, 0, 0,
                                                // 0, 0]
    }

四、AtomicIntegerFieldUpdater

AtomicIntegerFieldUpdater 可以对原子整数字段进行更新,要求:

  1. 字符必须使用volatile 修饰,使线程之间可见
  2. 只能是实例变量,不能是静态变量,也不能使用final 修饰
public class User {
    int id;
    volatile int age;

    public User(int id, int age) {
        this.id = id;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" + "id=" + id + ", age=" + age + '}';
    }
}
public class SubThread extends Thread {
    private User user; // 要更新的User 对象
    // 创建AtomicIntegerFieldUpdater 更新器
    private AtomicIntegerFieldUpdater<User> updater = AtomicIntegerFieldUpdater
            .newUpdater(User.class, "age");

    public SubThread(User user) {
        this.user = user;
    }

    @Override
    public void run() {
        // 在子线程中对user 对象的age 字段自增10 次
        for (int i = 0; i < 10; i++) {
            System.out.println(updater.getAndIncrement(user));
        }
    }
}
public static void main(String[] args) {
        User user = new User(1234, 10);
        // 开启10 个线程
        for (int i = 0; i < 10; i++) {
            new SubThread(user).start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(user);
    }

五、AtomicReference(可以原子读写一个对象)

// 创建一个AtomicReference 对象
    static AtomicReference<String> atomicReference = new AtomicReference<>("abc");

    public static void main(String[] args) throws InterruptedException {
        // 创建100 个线程修改字符串
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(20));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (atomicReference.compareAndSet("abc", "def")) {
                        System.out.println(Thread.currentThread().getName() + "把字符串abc 更改为def");
                    }
                }
            }).start();
        }
        // 再创建100 个线程
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(new Random().nextInt(20));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (atomicReference.compareAndSet("def", "abc")) {
                        System.out.println(Thread.currentThread().getName()
                                + "把字符串还原为abc");
                    }
                }
            }).start();
        }
        Thread.sleep(1000);
        System.out.println(atomicReference.get());
    }

六、AtomicStampedReference

AtomicStampedReference 原子类可以解决CAS 中的ABA 问题,在AtomicStampedReference 原子类中有一个整数标记值stamp, 每次执行CAS 操作时,需要对比它的版本,即比较stamp 的值

public class Test {
    // 定义AtomicStampedReference 引用操作"abc"字符串,指定初始化版本号为0
    private static AtomicStampedReference<String> stampedReference = new AtomicStampedReference<>(
            "abc", 0);

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                stampedReference.compareAndSet("abc", "def",
                        stampedReference.getStamp(),
                        stampedReference.getStamp() + 1);
                System.out.println(Thread.currentThread().getName() + "--"
                        + stampedReference.getReference());
                stampedReference.compareAndSet("def", "abc",
                        stampedReference.getStamp(),
                        stampedReference.getStamp() + 1);
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = stampedReference.getStamp(); // 获得版本号
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(stampedReference.compareAndSet("abc", "ggg",
                        stamp, stamp + 1));
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(stampedReference.getReference());
    }
}