Timetombs

泛义的工具是文明的基础,而确指的工具却是愚人的器物

66h / 117a
,更新于 2024-10-29T22:15:38Z+08:00 by   c73aa5b

[Java] Unsafe类

版权声明 - CC BY-NC-SA 4.0

Unsafe类是Java提供一个底层的类型,位于sun.misc包中,主要用来提供一些不安全的底层内存操作。

其内部实现在Jdk中略有差异,比如。Jdk 81和Jdk 152的源码。

1 获取Unsafe对象

由于Unsafe在实现上添加了限制(只能由BootstrapClassLoader加载的类型来调用,而我们的代码通常都是AppClassLoader加载的),所以我们无法直接通过Unsafe.getUnsafe()这个静态方法来获取。

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

//VM.isSystemDomainLoader
public static boolean isSystemDomainLoader(ClassLoader loader) {
  return loader == null;
}

但是可以通过反射绕过去这个限制:

public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
    Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafeField.setAccessible(true);
    return (Unsafe) theUnsafeField.get(null);
}

2 Unsafe提供的几种操作

大致分为一下几种。

2.1 获取字段偏移量

操作一个对象的某个字段时,偏移量时不可或缺的一个参数。使用它和对象本身来定位指定字段在内存中的位置。

public native long staticFieldOffset(Field f);
public native long objectFieldOffset(Field f);
public native Object staticFieldBase(Field f);
public native int arrayBaseOffset(Class<?> arrayClass);
public native int arrayIndexScale(Class<?> arrayClass)

2.2 普通读写

一下方法可以直接读取和写入一个对象的字段,即使字段是私有的。当然也包含其他的基础类型ByteCharShortFloatDoubleLongObject

public native int getInt(Object o, long offset);
public native void putInt(Object o, long offset, int x);

也可以直接使用脱离对象本身使用内存地址进行读取和写入。

public native int getInt(long address);
public native void putInt(long address, int x);

2.3 有序写

可以保证写入的有序性,但是无法保证其他线程的可见性。

public native void putOrderedInt(Object o, long offset, int x);

2.4 volatile读写

使用volatile修饰的字段也有专门的方法,可以保证可见性和有序性。

public native int getIntVolatile(Object o, long offset);
public native void putIntVolatile(Object o, long offset, int x);

2.5 CAS操作

CAS的底层实现是完全依赖Unsafe提供的方法的。

有如下三个基础方法。

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

JUC中的原子类,AQS、lock甚至synchronized的底层实现,也都依赖于这些基础CAS方法。ConcurrentHashMapConcurrentLinkedQueue等线程安全的容器也都是如此。

除了上面的3个基础方法外,还有一些包装好的方法。

public final int getAndAddInt(Object o, long offset, int delta) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, v + delta));
    return v;
}

public final int getAndSetInt(Object o, long offset, int newValue) {
    int v;
    do {
        v = getIntVolatile(o, offset);
    } while (!compareAndSwapInt(o, offset, v, newValue));
    return v;
}

以上读写部分等同于C#的System.Threading.Interlocked

2.6 类加载

。。。待续

2.7 内存屏障

// 保证在这个屏障之前的所有读操作都已经完成
public native void loadFence();
// 保证在这个屏障之前的所有写操作都已经完成
public native void storeFence();
// 保证在这个屏障之前的所有读写操作都已经完成
public native void fullFence();

2.8 线程调度

这部分方法类似于C#的System.Threading.Monitor

// 挂起线程
public native void park(boolean isAbsolute, long time);
// 唤醒线程
public native void unpark(Object thread);

// synchronized的底层实现基于此,不过目前以及标记为弃用了。
@Deprecated
public native void monitorEnter(Object o);
@Deprecated
public native void monitorExit(Object o);
@Deprecated
public native boolean tryMonitorEnter(Object o);

其中packuppack以及是LockSupport的基础。而LockSupportAQS的基础。AQS则是java.util.concurrent.locks包的基础抽象类。

pack的参数isAbsolutetrue是表示time是绝对时间,单位是毫秒。为false是表示为相对时间,单位是纳秒。比如如下是等价的。

// TIMED_WAITING 相对时间,100毫秒
unsafe.pack(false, 100*1000*1000));
// TIMED_WAITING 绝对时间,100毫秒
unsafe.pack(true, System.currentTimeMillis()+100));

// WAITING 一致阻塞,直到调用unpack
unsafe.pack(false, 0L));

3 参考资料

Java中的Unsafe :https://www.jianshu.com/p/db8dce09232d

上一篇 : [Java] JVM(Java Virtual Machine)
下一篇 : [Java] JMM(Java Memory Model)