Unsafe中CAS的实现

栏目: IT技术 · 发布时间: 4年前

内容简介:Unsafe 是位于 sun.misc 包下的一个类。Unsafe 提供的 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。由于并发相关的源码很多用到了 CAS,比如 java.util.concurrent.atomic 相关类、AQS、CurrentHashMap 等相关类。所以本文主要讲 Unsafe 中 CAS 的实现。笔者源码环境为主要相关源码CAS 是实现并发算法时常用到的一种技术。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 的用法及应用场景进行了基本介绍,仅做后续源码阅读的铺垫。到此,本篇文章就写完了,感谢大家的阅读!如果您觉得对您有帮助,请关注公众号【当我遇上你】。


以上所述就是小编给大家介绍的《Unsafe中CAS的实现》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

GWT in Action

GWT in Action

Robert Hanson、Adam Tacy / Manning Publications / 2007-06-05 / USD 49.99

This book will show Java developers how to use the Google Web Toolkit (GWT) to rapidly create rich web-based applications using their existing skills. It will cover the full development cycle, from ......一起来看看 《GWT in Action》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具