Unsafe 类中的重要方法
JDK的rt.jar 包中的 Unsafe 类提供了硬件级别的原子性操作,Unsafe 类中的方法都是native 方法,它们使用JNI的方式访问本地C++实现库。下面我们来了解一下 Unsafe 提供的几个主要的方法以及编程时如何使用 Unsafe 类做一些事情。
- long objectFieldOfset(Field feld)方法:返回指定的变量在所属类中的内存偏移地址,该偏移地址仅仅在该Unsafe函数中访问指定字段时使用。如下代码使用Unsafe 类获取变量 value 在 AtomicLong 对象中的内存偏移。
- int arrayBaseOffset(Class arrayClass)方法:获取数组中第一个元素的地址。
- int arrayIndexScale(Class arrayClass)方法:获取数组中一个元素占用的字节。
- boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比较对象 obj 中偏移量为 offset的变量的值是否与 expect 相等,相等则使用 update值更新,然后返回 true,否则返回 false。
- public native long getLongvolatile(Object obj, long offset)方法:获取对象 obj中偏移量为 offset 的变量对应 volatile 语义的值。
- void putLongvolatile(Object obj, long offset, long value)方法:设置 obj对象中 ofset偏移的类型为 long 的 feld 的值为 value,支持 volatile 语义。
- void putOrderedLong(Object obj, long offset, long value)万法:设置 obj 对象中 offset偏移地址对应的long型 feld 的值为 value。这是一个有延迟的 putLongvolatile 方法,并且不保证值修改对其他线程立刻可见。只有在变量使用volatile修饰并且预计会被意外修改时才使用该方法。
- void park(boolean isAbsolute,long time)方法:阻塞当前线程,其中参数isAbsolute等于 false 且 time 等于0表示一直阻塞。time 大于0表示等待指定的 time 后阻塞线程会被唤醒,这个time是个相对值,是个增量值,也就是相对当前时间累加time后当前线程就会被唤醒。如果isAbsolute等于true,并且time大于0,则表示阻塞的线程到指定的时间点后会被唤醒,这里time是个绝对时间,是将某个时间点换算为ms后的值。另外,当其他线程调用了当前阻塞线程的interrupt方法而中断了当前线程时,当前线程也会返回,而当其他线程调用了unPark方法并且把当前线程作为参数时当前线程也会返回。
- void unpark(Object thread)方法:唤醒调用 park 后阻塞的线程
下面是JDK8新增的函数,这里只列出 Long 类型操作
- long getAndSetLong(Object obj,long ofset, long update)方法:获取对象 obj 中偏移量为 offset 的变量 volatile 语义的当前值,并设置变量 volatile 语义的值为 update。
- long getAndAddLong(Object obj,long ofset, long addValue)方法:获取对象 obj 中偏移量为 ofset 的变量 volatile 语义的当前值,并设置变量值为原始值 +addValue。
- 类似 getAndSetLong的实现,只是这里进行CAS操作时使用了原始值+传递的增量参数 addValue 的值。
如何使用 Unsafe 类
public class UnsafeTest {
//获取 Unsafe的实例
// static final Unsafe unsafe = Unsafe.getUnsafe();
static final Unsafe unsafe;
//记录变量state在类TestUnsafe中的偏移值
static final long stateOffset;
//变量
private volatile long state = 1;
static {
try {
//使用反射获取Unsafe的成员变量theUnsafe
Field field = Unsafe.class.getDeclaredField("theUnsafe");
//设置为可存取
field.setAccessible(true);
//获取该变量的值
unsafe = (Unsafe) field.get(null);
//获取state变量在类TestUnsafe中的偏移值
stateOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("state"));
} catch (Exception ex) {
System.out.println(ex.getLocalizedMessage());
throw new Error(ex);
}
}
public static void main(String[] args) {
UnsafeTest test = new UnsafeTest();
boolean sucess = unsafe.compareAndSwapInt(test, stateOffset, 1, 2);
boolean sucess2 = unsafe.compareAndSwapInt(test, stateOffset, 2, 3);
System.out.println(sucess);
System.out.println(sucess2);
System.out.println(test.state);
}
}
这里通过反射,是由于看 getUnsafe方法做了特殊处理
public static Unsafe getUnsafe() {
//2.2.7
Class var0 = Reflection.getCallerClass();
//2.2.8
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
//2.2.9
public static boolean isSystemDomainLoader(ClassLoader var0) {
return var0 == null;
}
代码(2.2.7)获取调用getUnsafe 这个方法的对象的Class对象,这里是 UnSafeTest.class
代码(2.2.8)判断是不是Bootstrap类加载器加载的localClass,在这里是看是不是Bootstrap加载器加载了TestUnSafe.class。很明显由于TestUnSafe.class是使用AppClassLoader 加载的,所以这里直接抛出了异常。
思考一下,这里为何要有这个判断?我们知道Unsafe 类是rtjar 包提供的,rt.jar 包里面的类是使用 Bootstrap类加载器加载的,而我们的启动main函数所在的类是使用AppClassLoader 加载的,所以在 main 函数里面加载 Unsafe 类时,根据委托机制,会委托给 Bootstrap 去加载 Unsafe 类。
如果没有代码(2.2.8)的限制,那么我们的应用程序就可以随意使用 Unsafe 做事情了,而 Unsafe 类可以直接操作内存,这是不安全的,所以JDK开发组特意做了这个限制,不让开发人员在正规渠道使用Unsafe 类,而是在rt.jar 包里面的核心类中使用 Unsafe 功能。
如果开发人员想要使用Unsafe,只能使用万能的反射来实现。