Unsafe类是Java提供一个底层的类型,位于sun.misc
包中,主要用来提供一些不安全的底层内存操作。
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 普通读写
一下方法可以直接读取和写入一个对象的字段,即使字段是私有的。当然也包含其他的基础类型Byte
、Char
、Short
、Float
、Double
、Long
、Object
。
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方法。ConcurrentHashMap
、ConcurrentLinkedQueue
等线程安全的容器也都是如此。
除了上面的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);
其中pack
、uppack
以及是LockSupport
的基础。而LockSupport
是AQS
的基础。AQS
则是java.util.concurrent.locks包的基础抽象类。
pack
的参数isAbsolute
为true
是表示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
JDK8中的
sun.misc.Unsafe
: http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/misc/Unsafe.java ↩︎JDK15中的
sun.misc.Unsafe
: https://github.com/openjdk/jdk15/blob/master/src/jdk.unsupported/share/classes/sun/misc/Unsafe.java,内部是使用jdk.internal.misc.Unsafe
:https://github.com/openjdk/jdk15/blob/master/src/java.base/share/classes/jdk/internal/misc/Unsafe.java来实现,等同于JDK8。 ↩︎