CAS原理和源码解析

文章目录

  • 一、CAS是什么?
  • 二、CAS的原理
    • 1.CAS实现过程
    • 2.Unsafe实现CAS操作
  • 三、CAS的ABA问题


一、CAS是什么?

CAS是Compare And Swap(比较并替换)的缩写。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。CSA操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先对旧值进行比较,若没有发生变化,才交换新值,发生变化则不交换。

二、CAS的原理

1.CAS实现过程

线程会先去比较内存中的值与旧值是否相等,相等则将新值替换原来的旧值,否则自旋。(内存条在硬件方面保证了同一时刻只能有一个线程修改)实际java是通过JVM去调用操作系统,JVM是通过unsafe调用操作系统实现cas操作
CAS原理和源码解析_第1张图片

2.Unsafe实现CAS操作

代码示例:


import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class UnsafeDemo {
    volatile int i = 0;
    private static Unsafe unsafe;
    //i字段的偏移量,从逻辑上代表了i字段
    private static long valueOffset;
    static {
        //unsafe = Unsafe.getUnsafe();
        try {
            //通过反射获取unsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            //获取i字段的offset
            Field iField = CounterUnsafe.class.getDeclaredField("i");
            valueOffset = unsafe.objectFieldOffset(iField);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    public void add() {
        for (;;){
            //获取当前值(this当前对象的引用,valueOffset i的偏移量)
            int current = unsafe.getIntVolatile(this, valueOffset);
            //用CAS将值+1,成功退出自旋,失败则自旋
            if (unsafe.compareAndSwapInt(this, valueOffset, current, current+1))
                break;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        final UnsafeDemo ct = new UnsafeDemo();

        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        ct.add();
                    }
                    System.out.println("done...");
                }
            }).start();
        }

        Thread.sleep(6000L);
        System.out.println(ct.i);
    }
}

AtomicInteger也是通过偏移量来实现CAS操作的(java.util.concurrent.atomic包下面的类基本都是使用的Unsafe来实现CAS)
CAS原理和源码解析_第2张图片
CAS原理和源码解析_第3张图片

使用例子1:通过AtomicIntegerFieldUpdater实现对User属性的原子更新。
使用场景:已经存在的类,没有使用AtomicInteger来修饰属性(id,age),需要修改属性,就需要用
AtomicIntegerFieldUpdater来实现对属性进行CAS更新。注意:属性必须用volatile修饰,否则AtomicIntegerFieldUpdater没办法使用
CAS原理和源码解析_第4张图片
输出结果:在这里插入图片描述
使用例子2:这会出现原子性问题(多个线程获取都为null,然后都对owner修改),AtomicReference实现引用类型的更新,修改对象的引用。
CAS原理和源码解析_第5张图片
使用后:
在这里插入图片描述

三、CAS的ABA问题

1、ABA问题解释:线程1、线程2同时读取i=0;线程1、线程2都要执行CAS操作,如果线程2操作完成在线程1之后,那么线程1执行完成之后,线程1再执行CAS(0,1),将i的值改为0;

CAS原理和源码解析_第6张图片
最后的结果是线程1、线程2都执行成功(如果线程1执行完之后没有执行CAS(0,1),那么线程2执行CAS(0,1)会失败)。
问题原因:后面的1已经不是原来的1了
示例2:
CAS原理和源码解析_第7张图片

CAS原理和源码解析_第8张图片
解决ABA问题方法:加入版本号
存在ABA问题的代码示范:


import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

// 实现一个 栈(后进先出)
public class Stack {
    // top cas无锁修改
    AtomicReference<Node> top = new AtomicReference<Node>();

    public void push(Node node) { // 入栈
        Node oldTop;
        do {
            oldTop = top.get();
            node.next = oldTop;
        }
        while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
    }


    // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
    public Node pop(int time) {

        Node newTop;
        Node oldTop;
        do {
            oldTop = top.get();
            if (oldTop == null) {   //如果没有值,就返回null
                return null;
            }
            newTop = oldTop.next;
            if (time != 0) {    //模拟延时
                LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
            }
        }
        while (!top.compareAndSet(oldTop, newTop));     //将下一个节点设置为top
        return oldTop;      //将旧的Top作为值返回
    }
}

使用添加版本号的代码示例:


import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;

public class ConcurrentStack {
    // top cas无锁修改
    //AtomicReference top = new AtomicReference();
    AtomicStampedReference<Node> top =
            new AtomicStampedReference<>(null, 0);

    public void push(Node node) { // 入栈
        Node oldTop;
        int v;
        do {
            v = top.getStamp();
            oldTop = top.getReference();
            node.next = oldTop;
        }
        while (!top.compareAndSet(oldTop, node, v, v+1)); // CAS 替换栈顶
    }


    // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
    public Node pop(int time) {

        Node newTop;
        Node oldTop;
        int v;

        do {
            v = top.getStamp();
            oldTop = top.getReference();
            if (oldTop == null) {   //如果没有值,就返回null
                return null;
            }
            newTop = oldTop.next;
            if (time != 0) {    //模拟延时
                LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
            }
        }
        while (!top.compareAndSet(oldTop, newTop, v, v+1));     //将下一个节点设置为top
        return oldTop;      //将旧的Top作为值返回
    }
}

AtomicStampedReference此类带有版本号,替换原有的 AtomicReference,这样解决了ABA问题

你可能感兴趣的:(java)