Unsafe源码分析

yPhantom 2019年10月31日 29次浏览

参考美团技术博客

根据Unsafe的内部实现,Unsafe类是个单例,仅能通过getUnsafe()函数获取实例。

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

而在get的过程中又用VM.isSystemDomainLoader来判断是否为引导类加载器。因此有两个方案可以获取。

1 将调用类加入到bootclasspath中

Java 命令行提供了如何扩展bootStrap 级别class的简单方法.

  • -Xbootclasspath: 完全取代基本核心的Java class 搜索路径. 不常用,否则要重新写所有Java 核心class
  • -Xbootclasspath/a: 后缀在核心class搜索路径后面.常用!!
  • -Xbootclasspath/p: 前缀在核心class搜索路径前面.不常用,避免 引起不必要的冲突.
java -Xbootclasspath/a:/usrhome/thirdlib.jar: -jar yourJarExe.jar

java在启动的时候会用引导类加载器BootstrapClassLoader加载a(append)后的jar包。如果jar包里面调用了Unsafe.getUnsafe(),就能成功。

2 通过反射

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;
    }
}

getFieldgetDeclaredField的区别:

两者都是反射中获取类的字段。getField获取是public的字段,getDeclaredField获取是一个类的所有字段。底层调用的是同一个native方法,但参数不同

CAS相关

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

CAS问题

  1. ABA问题

CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。 常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。 目前在JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

  1. 循环时间长开销大

如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。