ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

CAS操作及原子类的使用

2022-05-25 11:33:34  阅读:204  来源: 互联网

标签:var1 var2 CAS Object long 原子 操作 public native


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方法,直到期望的值和真实值一样

  • val1:AtomicInteger对象本身

  • var2:该对象值得引用地址

  • var4:需要变动的数值

  • var5:用var1和var2找到的内存中的真实值

    • 用该对象当前的值与var5比较
    • 如果相同,更新var5 + var4 并返回true
    • 如果不同,继续取值然后再比较,直到更新完成

这里没有用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不加锁,保证一次性,但是需要多次比较

  • 循环时间长,开销大(因为执行的是do while,如果比较不成功一直在循环,最差的情况,就是某个线程一直取到的值和预期值都不一样,这样就会无限循环)
  • 只能保证一个共享变量的原子操作
    • 当对一个共享变量执行操作时,我们可以通过循环CAS的方式来保证原子操作
    • 但是对于多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁来保证原子性,也可以将多个变量分装成为一个类,使用AtomicReference来操作。
  • ABA问题
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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有