Unsafe中CAS的实现

前言

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

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。bootstrap

说明:对象的基地址 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));

    }
}

结果输出atom

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

Unsafe 中 CAS 操做是原子性的,因此在秒杀、库存扣减中也能够使用 Unsafe 来扣减库存。spa

结语

本文对 Java 中的 sun.misc.Unsafe 的用法及应用场景进行了基本介绍,仅作后续源码阅读的铺垫。到此,本篇文章就写完了,感谢你们的阅读!若是您以为对您有帮助,请关注公众号【当我赶上你】。命令行

相关文章
相关标签/搜索