Java 并发编程之CAS 和 Unsafe类本地使用方法

Java 并发编程之CAS 和 Unsafe类本地使用方法

CAS原理与Unsafe类

我们知道保证线程安全的三个要素是原子性,可见性,有序性

CAS(Compare And Swap),指令级别保证某一内存地址V上的值的更新修改是一个原子操作
需要三个值:

  • 内存地址V

  • 该线程拿到的值A

  • 期望更新后的值B

思路:如果地址V上的实际值和该线程拿到的值A相等,就给地址V赋给新值B,如果不是,不做任何操作。
循环(死循环,自旋)里不断的进行CAS操作

JDK里为我们提供了这些原子操作类

  • 更新基本类型类:AtomicBooleanAtomicIntegerAtomicLongAtomicReference
  • 更新数组类:AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray
  • 更新引用类型:AtomicReferenceAtomicMarkableReferenceAtomicStampedReference
  • 原子更新字段类: AtomicReferenceFieldUpdaterAtomicIntegerFieldUpdaterAtomicLongFieldUpdater

观察这些类的源码我们可以发现,CAS底层的原理实现都需要借助一个Unsafe类来实现,比如对于AtomicInteger类的compareAndSet方法:

    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

代码中unsafe变量的初始化:

private static final Unsafe unsafe = Unsafe.getUnsafe();

于是,为了尝试使用CAS在本地操作,模仿了上面的代码和初始化,尝试在本地进行测试,代码如下:

public class CASTest {
    static volatile long valueOffset;
    // Unsafe类初始化
    static Unsafe unsafe = sun.misc.Unsafe.getUnsafe();


    static {
//        initUnsafe();
        try {
            valueOffset = unsafe.objectFieldOffset
                    (CASTest.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    
    private static void initUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe1 =  (Unsafe) f.get(null);
            unsafe = unsafe1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    
    
    volatile int value;

    public CASTest(int value) {
        this.value = value;
    }
	
    // 测试cas操作
    public void cas() {
        System.out.println(unsafe.compareAndSwapInt(this, valueOffset , 5, 10));
        System.out.println("this.value:" + this.value);
        System.out.println(unsafe.compareAndSwapInt(this, valueOffset , 5, 20));
        System.out.println("this.value:" + this.value);
    }
    public static void main(String[] args) {
        new CASTest(5).cas();
    }
}

但是执行后发现,会报错:

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.SecurityException: Unsafe
	at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
	at com.lagou.concurrent.demo.test.CASTest.<clinit>(CASTest.java:9)

查询相关资料后发现,在使用该getUnsafe方法是,会判断classLoader的类型,如果不是systemClassLoader则会抛出SecurityException(“Unsafe”)异常,所以用户编写的程序使用不了unsafe实例。

那如果我们想本地实现可以怎么办呢?

Unsafe类本地使用方法

下面给出一个本地使用Unsafe类初始化的方法,也是网上使用比较多的方法

private static void initUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe1 =  (Unsafe) f.get(null);
            unsafe = unsafe1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

使用该初始化方法更新上面的测试代码:

public class CASTest {
    static volatile long valueOffset;
    static Unsafe unsafe;


    static {
        initUnsafe();
        try {
            valueOffset = unsafe.objectFieldOffset
                    (CASTest.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private static void initUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            Unsafe unsafe1 =  (Unsafe) f.get(null);
            unsafe = unsafe1;
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
    volatile int value;

    public CASTest(int value) {
        this.value = value;
    }

    public void cas() {
        System.out.println(unsafe.compareAndSwapInt(this, valueOffset , 5, 10));
        System.out.println("this.value:" + this.value);
        System.out.println(unsafe.compareAndSwapInt(this, valueOffset , 5, 20));
        System.out.println("this.value:" + this.value);
    }
    public static void main(String[] args) {
        new CASTest(5).cas();
    }
}

正常执行,且输出结果为:

true
this.value:10
false
this.value:10

对于输出结果,根据CAS原理我们分析可知,初始时,我们赋值value=5,那么第一个CAS操作时valueOffset地址对应的value值=5,与compareAndSwapInt参数里的期望值5匹配,因此CAS操作成功返回true,同时value值被赋为10。同理,第二次CAS操作取valueOffset地址对应的value=10,与方法中的期望值5不匹配,则CAS操作失败返回false,此时value的值仍为10。

如果我们把第二次CAS操作的期望值设成10,那么最终的返回value值会为20。

true
this.value:10
true
this.value:20
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

JermeryBesian

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值