• 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);
        }
    
    • 1
    • 2
    • 3

    代码中unsafe变量的初始化:

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

    于是,为了尝试使用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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

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

    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)
    
    • 1
    • 2
    • 3
    • 4

    查询相关资料后发现,在使用该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();
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

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

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

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

    true
    this.value:10
    false
    this.value:10
    
    • 1
    • 2
    • 3
    • 4

    对于输出结果,根据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
    
    • 1
    • 2
    • 3
    • 4
  • 相关阅读:
    Sampling Area Lights
    使用邮箱发送验证码完成注册
    分布式事务详解
    编程-设计模式 7:桥接模式
    LeetCode题练习与总结:统计词频--192
    【golang】1769. 移动所有球到每个盒子所需的最小操作数---时间复杂度O(N)
    【计算机视觉】深度学习框架-Keras
    一些可以参考的文档集合6
    Redis 管道详解
    Java项目源码基于JavaWeb实现的停车场车位收费管理系统
  • 原文地址:https://blog.csdn.net/Urbanears/article/details/128097550