【Java】聊聊多线程中常用的Unsafe
聊聊多线程中常用的Unsafe
小强大人发布于 今天 14:43
前言
看过JUC源码的同学对Unsafe应该都不陌生,很多JUC包下的类内部都是用它来实现的CAS操作,可见它的威力和重要性。为什么它的命名是“不安全”呢?因为它可以直接访问和操作底层内存资源,这对于程序员来说,如果过度或者不正确的使用Unsafe类,就变得不安全了。
如何获取它的实例?
我们先来看看它的内部结构:
public final class Unsafe {private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
// 获取调用方的Class
Class var0 = Reflection.getCallerClass();
// 判断调用方是否是引导类加载器加载的
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
可以看到,Unsafe类是单例的,那我们应用程序可以直接调用getUnsafe()方法拿到它的实例吗?不妨来试试看:
import sun.misc.Unsafe;public class TryGetUnsafe {
public static void main(String[] args) {
Unsafe theUnsafe = Unsafe.getUnsafe();
System.out.println(theUnsafe.toString());
}
}
运行结果如下:
仔细看了getUnsafe()源码的同学,都不难理解为什么抛出这个异常,因为它有个安全控制,若调用方的Class不是引导类加载器所加载的,就会抛出异常SecurityException("Unsafe"),它是怎么判断是否引导类加载器加载的呢?代码也很简单:
public static boolean isSystemDomainLoader(ClassLoader var0) {return var0 == null;
}
回忆下类加载机制的双亲委派模型,最顶层就是引导类加载器,它是用C++语言实现的,不是Java的类,对应的ClassLoader实例就为null。
那如果我们应用程序就想拿到Unsafe实例,有什么办法吗?
方法有2种:
①从它的限制条件出发,让我们编写的应用程序所在的类被引导类加载器所加载即可,具体怎么做呢?
通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类所在jar包路径追加到默认的bootstrap路径中,即:
java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
②强大的反射机制,直接上代码:
import sun.misc.Unsafe;import java.lang.reflect.Field;
public class TryGetUnsafe {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafeField.get(null);
System.out.println(unsafe.toString());
}
}
运行结果如下:
说明我们已经成功获取了Unsafe实例。
常用方法
public native long objectFieldOffset(Field field) :获取指定的属性Field在所属类中的内存偏移地址。这个方法很多类都会使用到,如我另一篇文章ThreadLocalRandom讲到的源码:
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update):比较对象obj中偏移量为offset的属性值,是否与预期值expect相等,如果相等就替换成update值,然后返回true,否则返回false。即我们常说的CAS底层实现。还有类似的方法compareAndSwapInt,compareAndSwapObject等。
public native long getLongVolatile(Object obj, long offset):获取对象obj中偏移量为offset的属性对应volatile语义的值。
public native void putLongVolatile(Object obj, long offset, long value):设置对象obj中偏移量为offset的属性值为value,这个操作同样也是带volatile语义的。
public final long getAndAddLong(Object obj, long offset, long addValue):获取对象obj中偏移量为offset的属性值,并设置新值=旧值+addValue,注意返回的是旧值。这个方法没有native修饰符,是java写的,源码如下:
public final long getAndAddLong(Object obj, long offset, long addValue) {long l;
do {
l = this.getLongVolatile(obj, offset);
} while(!this.compareAndSwapLong(obj, offset, l, l + addValue));
return l;
}
public native int arrayBaseOffset(Class<?> arrayClass):获取数组的第一个元素地址。
public native int arrayIndexScale(Class<?> arrayClass):获取数组每个元素占用的字节数。
public native void park(boolean isAbsolute, long time):阻塞当前线程,其中参数isAbsolute表示是否为绝对时间,isAbsolute为false且time为0时表示一直阻塞,time大于0表示等待time时间段后线程被唤醒,如果isAbsoluete为true,那time表示的是某个时间点的时间戳毫秒值。当其他线程调用unpark,且参数为此线程时,此线程就会被唤醒。其他线程调用interrupt方法时,同样也会唤醒。
public native void unpark(Object thread):唤醒某个调用park方法的线程thread。
常用的方法就介绍这么多,其实还有很多其他方法,有兴趣的同学可以去翻看源码。
阅读 42更新于 今天 14:47
本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议
小强大人
4 声望
1 粉丝
小强大人
4 声望
1 粉丝
宣传栏
前言
看过JUC源码的同学对Unsafe应该都不陌生,很多JUC包下的类内部都是用它来实现的CAS操作,可见它的威力和重要性。为什么它的命名是“不安全”呢?因为它可以直接访问和操作底层内存资源,这对于程序员来说,如果过度或者不正确的使用Unsafe类,就变得不安全了。
如何获取它的实例?
我们先来看看它的内部结构:
public final class Unsafe {private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
// 获取调用方的Class
Class var0 = Reflection.getCallerClass();
// 判断调用方是否是引导类加载器加载的
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
}
可以看到,Unsafe类是单例的,那我们应用程序可以直接调用getUnsafe()方法拿到它的实例吗?不妨来试试看:
import sun.misc.Unsafe;public class TryGetUnsafe {
public static void main(String[] args) {
Unsafe theUnsafe = Unsafe.getUnsafe();
System.out.println(theUnsafe.toString());
}
}
运行结果如下:
仔细看了getUnsafe()源码的同学,都不难理解为什么抛出这个异常,因为它有个安全控制,若调用方的Class不是引导类加载器所加载的,就会抛出异常SecurityException("Unsafe"),它是怎么判断是否引导类加载器加载的呢?代码也很简单:
public static boolean isSystemDomainLoader(ClassLoader var0) {return var0 == null;
}
回忆下类加载机制的双亲委派模型,最顶层就是引导类加载器,它是用C++语言实现的,不是Java的类,对应的ClassLoader实例就为null。
那如果我们应用程序就想拿到Unsafe实例,有什么办法吗?
方法有2种:
①从它的限制条件出发,让我们编写的应用程序所在的类被引导类加载器所加载即可,具体怎么做呢?
通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类所在jar包路径追加到默认的bootstrap路径中,即:
java -Xbootclasspath/a: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
②强大的反射机制,直接上代码:
import sun.misc.Unsafe;import java.lang.reflect.Field;
public class TryGetUnsafe {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe)theUnsafeField.get(null);
System.out.println(unsafe.toString());
}
}
运行结果如下:
说明我们已经成功获取了Unsafe实例。
常用方法
public native long objectFieldOffset(Field field) :获取指定的属性Field在所属类中的内存偏移地址。这个方法很多类都会使用到,如我另一篇文章ThreadLocalRandom讲到的源码:
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update):比较对象obj中偏移量为offset的属性值,是否与预期值expect相等,如果相等就替换成update值,然后返回true,否则返回false。即我们常说的CAS底层实现。还有类似的方法compareAndSwapInt,compareAndSwapObject等。
public native long getLongVolatile(Object obj, long offset):获取对象obj中偏移量为offset的属性对应volatile语义的值。
public native void putLongVolatile(Object obj, long offset, long value):设置对象obj中偏移量为offset的属性值为value,这个操作同样也是带volatile语义的。
public final long getAndAddLong(Object obj, long offset, long addValue):获取对象obj中偏移量为offset的属性值,并设置新值=旧值+addValue,注意返回的是旧值。这个方法没有native修饰符,是java写的,源码如下:
public final long getAndAddLong(Object obj, long offset, long addValue) {long l;
do {
l = this.getLongVolatile(obj, offset);
} while(!this.compareAndSwapLong(obj, offset, l, l + addValue));
return l;
}
public native int arrayBaseOffset(Class<?> arrayClass):获取数组的第一个元素地址。
public native int arrayIndexScale(Class<?> arrayClass):获取数组每个元素占用的字节数。
public native void park(boolean isAbsolute, long time):阻塞当前线程,其中参数isAbsolute表示是否为绝对时间,isAbsolute为false且time为0时表示一直阻塞,time大于0表示等待time时间段后线程被唤醒,如果isAbsoluete为true,那time表示的是某个时间点的时间戳毫秒值。当其他线程调用unpark,且参数为此线程时,此线程就会被唤醒。其他线程调用interrupt方法时,同样也会唤醒。
public native void unpark(Object thread):唤醒某个调用park方法的线程thread。
常用的方法就介绍这么多,其实还有很多其他方法,有兴趣的同学可以去翻看源码。
以上是 【Java】聊聊多线程中常用的Unsafe 的全部内容, 来源链接: utcz.com/a/108555.html
得票时间