扫描下方二维码或者微信搜索公众号
菜鸟飞呀飞
,即可关注微信公众号,阅读更多Spring源码分析
和Java并发编程
文章。
文章目录
在上一篇文章《初始CAS的实现原理》中,提到了Unsafe类相关方法,今天这篇文章将详细介绍Unsafe类的源码。
为什么要单独用一篇文章介绍Unsafe类呢?这是因为在看源码过程中,经常会碰到它,例如JUC包下的原子类、AQS、Netty等源码中,最终都会看见Unsafe类的使用。搞清楚Unsafe类的使用,对以后看源码会有很大的帮助。
1. Unsafe类简介
- Unsafe类是
rt.jar
中sun.misc
包下的类,从类名就能看出来,这个类是不安全的,但是它的功能十分强大。相比C和C++的开发人员,作为一名Java开发人员是十分幸福的,因为在Java中程序员在开发时不需要关注内存的管理,对象的回收,因为JVM全部都帮助我们完成了。如果Java开发人员需要自己手动去操作内存,那么可以通过Unsafe类去进行申请,这也是Unsafe类被定义为不安全
的类的原因,因为一不小心就容易出现忘记释放内存
等问题。 - Unsafe类中方法很多,但大致可以分为8大类。CAS操作、内存操作、线程调度、数组相关、对象相关操作、Class相关操作、内存屏障相关、系统相关。
笔者画了一张脑图,因为图片占用空间较大,为了不影响阅读,我把这张图放在了文章末尾,以供参考。
2. 如何获取Unsafe类的实例
- Unsafe类被final修饰了,表示Unsafe不能被继承;同时Unsafe的构造方法用private修饰,表示外部无法直接通过构造方法去创建实例。实际上Unsafe是一个单例对象,下面是Unsafe类的部分源码。
// 类被final修饰,表示不能被继承
public final class Unsafe {
// 构造器被私有化
private Unsafe() {
}
private static final Unsafe theUnsafe = new Unsafe();
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
}
- 虽然Unsafe是一个单例,但是我们在自己开发的类中无法通过Unsafe.getUnsafe()获取到Unsafe的实例,在程序运行时会抛出
SecurityException
异常。例如如下示例:
public class Demo {
public static void main(String[] args) {
Unsafe unsafe = Unsafe.getUnsafe();
}
}
- 运行main()方法,最终在控制台出现如下运行时异常:
Exception in thread "main" java.lang.SecurityException: Unsafe
at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
at com.tiantang.study.Demo.main(Demo.java:14)
- 为什么会出现
SecurityException
异常呢?这是因为在Unsafe类的getUnsafe()
方法中,它做了一层校验,判断当前类(Demo)的类加载器(ClassLoader)是不是启动类加载器(Bootstrap ClassLoader)
,如果不是,则会抛出SecurityException
异常。在JVM的类加载机制中,自定义的类使用的类加载器是应用程序类加载器(Application ClassLoader)
,所以这个时候校验失败,会抛出异常。 - 那么如何才能获取到Unsafe类的实例呢?有两种方案。
- 第一方案:将我们自定义的类(如Demo类)所在的jar包所在的路径通过-Xbootclasspath参数添加到Java命令中,这样当程序启动时,Bootstrap ClassLoader会加载Demo类,这样校验就通过了。显然这种方式比较麻烦,而且不太实用,因为在项目中,可能需要在很多地方都使用Unsafe类,如果通过Java命令行这种方式去指定,就会很麻烦,而且容易出现纰漏。
- 第二种方案:通过反射来创建Unsafe类的实例(
反射反射,程序员的快乐
)。反射的代码可以参考如下示例:
public static void main(String[] args) {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 将字段的访问权限设置为true
field.setAccessible(true);
// 因为theUnsafe字段在Unsafe类中是一个静态字段,所以通过Field.get()获取字段值时,可以传null获取
Unsafe unsafe = (Unsafe)