• Unsafe中CAS的实现


    前言

    Unsafe 是位于 sun.misc 包下的一个类。Unsafe 提供的 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。由于并发相关的源码很多用到了 CAS,比如 java.util.concurrent.atomic 相关类、AQS、CurrentHashMap 等相关类。所以本文主要讲 Unsafe 中 CAS 的实现。笔者源码环境为 OpenJDK8

    CAS 相关

    主要相关源码

        /**
         * 参数说明
         * @param o             包含要修改field的对象
         * @param offset        对象中某个参数field的偏移量,该偏移量不会改变
         * @param expected      期望该偏移量对应的field值
         * @param x             更新值
         * @return              true|false
         */
        public final native boolean compareAndSwapObject(Object o, long offset,
                                                         Object expected,
                                                         Object x);
    
        public final native boolean compareAndSwapInt(Object o, long offset,
                                                      int expected,
                                                      int x);
    
        public final native boolean compareAndSwapLong(Object o, long offset,
                                                       long expected,
                                                       long x);
    

    CAS 是实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的 原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底层实现即为 CPU 指令 cmpxchg。

    说明:对象的基地址 baseAddress+valueOffset 得到 value 的内存地址 valueAddress

    Unsafe 类获取

    首先看下 Unsafe 的单例实现

        private static final Unsafe theUnsafe = new Unsafe();
        // 注解表明需要引导类加载器
        @CallerSensitive
        public static Unsafe getUnsafe() {
            Class<?> caller = Reflection.getCallerClass();
            // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
            if (!VM.isSystemDomainLoader(caller.getClassLoader()))
                throw new SecurityException("Unsafe");
            return theUnsafe;
        }
    

    那如若想使用这个类,该如何获取其实例?有如下两个可行方案。

    其一,从 getUnsafe 方法的使用限制条件出发,通过 Java 命令行命令 -Xbootclasspath/a 把调用 Unsafe 相关方法的类 A 所在 jar 包路径追加到默认的 bootstrap 路径中,使得 A 被引导类加载器加载,从而通过 Unsafe.getUnsafe 方法安全的获取 Unsafe 实例。

    java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径
    

    其二,通过反射获取单例对象 theUnsafe。

    @Slf4j
    public class UnsafeTest {
    
        private static Unsafe reflectGetUnsafe() {
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                return (Unsafe) field.get(null);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
    
        public static void main(String[] args) {
            Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
        }
    }
    

    CAS 演练

    1. 创建一个类
    @Getter@Setter
    public class User {
        private String name;
        private int age;
    }
    
    1. 反射获取 Unsafe 并测试 CAS
    @Slf4j
    public class UnsafeTest {
    
        private static Unsafe reflectGetUnsafe() {
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                return (Unsafe) field.get(null);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
                return null;
            }
        }
    
        public static void main(String[] args) throws Exception{
            Unsafe unsafe = UnsafeTest.reflectGetUnsafe();
            // allocateInstance: 对象操作。绕过构造方法、初始化代码来创建对象
            User user = (User)unsafe.allocateInstance(User.class);
            user.setName("admin");
            user.setAge(17);
    
    
            Field name = User.class.getDeclaredField("name");
            Field age = User.class.getDeclaredField("age");
    
            // objectFieldOffset: 返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
            long nameOffset = unsafe.objectFieldOffset(name);
            long ageOffset = unsafe.objectFieldOffset(age);
    
            System.out.println("name内存偏移地址:" + nameOffset);
            System.out.println("age 内存偏移地址:" + ageOffset);
    
            System.out.println("---------------------");
    
            // CAS操作
            int currentValue = unsafe.getIntVolatile(user, ageOffset);
            System.out.println("age内存当前值:" + currentValue);
            boolean casAge = unsafe.compareAndSwapInt(user, ageOffset, 17, 18);
            System.out.println("age进行CAS更新成功:" + casAge);
            System.out.println("age更新后的值:" + user.getAge());
    
            System.out.println("---------------------");
    
            // volatile修饰,保证可见性、有序性
            unsafe.putObjectVolatile(user, nameOffset, "test");
            System.out.println("name更新后的值:" + unsafe.getObjectVolatile(user, nameOffset));
    
        }
    }
    

    结果输出

    name内存偏移地址:16
    age 内存偏移地址:12
    ---------------------
    age内存当前值:17
    age进行CAS更新成功:true
    age更新后的值:18
    ---------------------
    name更新后的值:test
    

    Unsafe 中 CAS 操作是原子性的,所以在秒杀、库存扣减中也可以使用 Unsafe 来扣减库存。

    结语

    本文对 Java 中的 sun.misc.Unsafe 的用法及应用场景进行了基本介绍,仅做后续源码阅读的铺垫。到此,本篇文章就写完了,感谢大家的阅读!如果您觉得对您有帮助,请关注公众号【当我遇上你】。

  • 相关阅读:
    职场“十不要”,让你少奋斗30年
    360与QQ在用户界面上的明显BUG
    urlMappings在asp.net2.0,asp.net4.0中的差异
    NHibernate主键生成方式
    MDaemon 常用视频教程
    sqlserver 差异备份与还原示例
    没有不死的爱情, 只有平淡的亲情——如何维系我们的婚姻
    25 个在 Web 中嵌入图表的免费资源
    atoi,atol,strtod,strtol,strtoul实现类型转换
    人生之精华,胜读十年书
  • 原文地址:https://www.cnblogs.com/idea360/p/12496547.html
Copyright © 2020-2023  润新知