其他分享
首页 > 其他分享> > CAS操作及原子类的使用

CAS操作及原子类的使用

作者:互联网

CAS操作及原子类的使用

1、CAS操作

在java中,加锁可以解决一定的并发问题,但是锁有一个不好的问题就是当一个线程没有获取到锁的时候就会被阻塞挂起,这会导致线程上下文的切换和重新调度开销。Java提供了非阻塞的volatile关键字来解决共享变量的可见性问题,这在一定程度上弥补了锁带来的开销问题,但是volatile不能保证原子性问题。

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作,再次强调,由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,也就是说CAS是线程安全的。

2、原子类操作及原理剖析

JUC包提供了一系列原子操作类,这些类都是使用非阻塞算法CAS实现的,相比使用锁实现原子性操作这在性能上会有很大的提高。JUC并发包中包含有AtomicInteger、AtomicLong、AtomicBoolean、AtomicReference等一系列原子操作类。

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    // 获取unsafe类的实例
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    // 存放value的偏移量
    private static final long valueOffset;

    static {
        try {
            // 获取value在AtomicInteger中的偏移量
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
	// 实际变量值, 声明为volatile主要是为了在多线程下保证内存可见性
    private volatile int value;

    /**
     * Creates a new AtomicInteger with the given initial value.
     *
     * @param initialValue the initial value
     */
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    /**
     * Creates a new AtomicInteger with initial value {@code 0}.
     */
    public AtomicInteger() {
    }
    ....
}
Unsafe类中的重要方法

JDK的rt.jar包中的Unsafe类提供了硬件级别的原子性操作,Unsafe类中的方法都是native方法,它们使用JNI的方式访问本地C++实现库。

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

public native Object getObjectVolatile(Object var1, long var2);

public native void putObjectVolatile(Object var1, long var2, Object var4);

public native int getIntVolatile(Object var1, long var2);

public native void putIntVolatile(Object var1, long var2, int var4);

public native boolean getBooleanVolatile(Object var1, long var2);

public native void putBooleanVolatile(Object var1, long var2, boolean var4);

public native byte getByteVolatile(Object var1, long var2);

public native void putByteVolatile(Object var1, long var2, byte var4);

public native short getShortVolatile(Object var1, long var2);

public native void putShortVolatile(Object var1, long var2, short var4);

public native char getCharVolatile(Object var1, long var2);

public native void putCharVolatile(Object var1, long var2, char var4);

public native long getLongVolatile(Object var1, long var2);

public native void putLongVolatile(Object var1, long var2, long var4);

public native float getFloatVolatile(Object var1, long var2);

public native void putFloatVolatile(Object var1, long var2, float var4);

public native double getDoubleVolatile(Object var1, long var2);

public native void putDoubleVolatile(Object var1, long var2, double var4);

public native void putOrderedObject(Object var1, long var2, Object var4);

public native void putOrderedInt(Object var1, long var2, int var4);

public native void putOrderedLong(Object var1, long var2, long var4);

public native void unpark(Object var1);

public native void park(boolean var1, long var2);
AtomicInteger递增和递减操作代码

调用unsafe方法,原子性设置value值为原始值+1,返回值为递增后的结果

public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}

调用unsafe方法,原子性设置value值为原始值-1,返回值为递减之后的结果

public final int decrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}

调用unsafe方法,原子性设置value值为原始值+1,返回值为原始值

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

调用unsafe方法,原子性设置value值为原始值-1,返回值为原始值

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}

其上四个代码都是通过调用unsafe的getAndAddInt方法实现的,这个函数是个原子性操作,第一个参数是AtomicInteger实例的引用,第二个参数是value变量在AtomicInteger中的偏移值,第三个参数是要设置的第二个变量的值。

其中unsafe.getAndAddInt的代码为:

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

var5:就是我们从主内存中拷贝到工作内存中的值(每次都要从主内存拿到最新的值到自己的本地内存,然后执行compareAndSwapInt()在再和主内存的值进行比较。因为线程不可以直接越过高速缓存,直接操作主内存,所以执行上述方法需要比较一次,在执行加1操作)

假设执行 compareAndSwapInt返回false,那么就一直执行 while方法,直到期望的值和真实值一样

这里没有用synchronized,而用CAS,这样提高了并发性,也能够实现一致性,是因为每个线程进来后,进入的do while循环,然后不断的获取内存中的值,判断是否为最新,然后在进行更新操作。

AtomicInteger中的compareAndSet方法
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

由此段代码可以得知,其内部调用的unsafe.compareAndSwapInt方法,如果原子变量中的值等于expect,则使用update值更新该值并返回true,否则返回false。

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author Wenbo
 * @version 1.0
 * @program
 * @description
 * @date 2022/5/25 10:18
 */
public class AtomicTest {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(10);

        System.out.println(atomicInteger.compareAndSet(10,2019) + "\t 当前值" + atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(10,1024) + "\t 当前值" + atomicInteger.get());
    }
}

这是因为我们执行第一个的时候,期望值和原本值是满足的,因此修改成功,但是第二次后,主内存的值已经修改成了2019,不满足期望值,因此返回了false,本次写入失败。

CAS缺点

CAS不加锁,保证一次性,但是需要多次比较

ABA问题是什么

CAS操作的经典问题,假如线程1使用CAS修改初始值A为变量X,那么线程1会首先去获取当前变量X的值(为A),然后使用CAS操作尝试修改X的值为B,如果使用CAS操作成功了,程序其实不一定是正确的,因为有可能线程1获取变量X的值A后,在执行CAS前,线程2使用CAS修改了变量X的值为B,然后又使用CAS修改了变量X的值为A。所以虽然线程1执行CAS时X的值为A,但是这个A已经不是线程1获取使得A了,这就是ABA问题。

ABA问题的产生是因为变量的状态值产生了环形转换,就是变量的值可以从A到B,然后再从B到A。如果变量的值只能朝着一个方向转换,比如A->B->C,不构成环形,就不会存在问题。

基于原子引用的ABA问题
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author Wenbo
 * @version 1.0
 * @program
 * @description
 * @date 2022/5/25 10:56
 */
public class ABATest {
    /**
     * 普通的原子引用包装类
     */
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    public static void main(String[] args) {

        new Thread(() -> {
            // 把100 改成 101 然后在改成100,也就是ABA
            atomicReference.compareAndSet(100, 101);
            atomicReference.compareAndSet(101, 100);
        }, "t1").start();

        new Thread(() -> {
            try {
                // 睡眠一秒,保证t1线程,完成了ABA操作
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 把100 改成 101 然后在改成100,也就是ABA
            System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());

        }, "t2").start();
    }
}

创建了两个线程,然后t1线程执行依次ABA操作,t2线程在1秒后修改主内存的值,结果可以看到它能够修改成功,这就是ABA问题。

AtomicStampedReference解决ABA问题

AtomicStampedReference类给每个变量的状态值配备一个时间戳,每次更新的时候需要比较期望值和当前值,以及期望版本号和当前版本号,从而避免ABA问题的产生。数据库中乐观锁的思想。

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

/**
 * @author Wenbo
 * @version 1.0
 * @program
 * @description
 * @date 2022/5/25 10:56
 */
public class ABATest {
    /**
     * 普通的原子引用包装类
     */
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    // 传递两个值,一个是初始值,一个是初始版本号
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {

            // 获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);

            // 暂停t3一秒钟
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 传入4个值,期望值,更新值,期望版本号,更新版本号
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);

            System.out.println(Thread.currentThread().getName() + "\t 第二次版本号" + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1);

            System.out.println(Thread.currentThread().getName() + "\t 第三次版本号" + atomicStampedReference.getStamp());

        }, "t3").start();

        new Thread(() -> {

            // 获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t 第一次版本号" + stamp);

            // 暂停t4 3秒钟,保证t3线程也进行一次ABA问题
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp+1);

            System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:" + atomicStampedReference.getStamp());

            System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值" + atomicStampedReference.getReference());


        }, "t4").start();

    }
}

标签:var1,var2,CAS,Object,long,原子,操作,public,native
来源: https://www.cnblogs.com/youngerwb/p/16308664.html