简介
Unsafe是一个比较底层的类,坐落sun.misc包下。取名是Unsafe并不意味着线程不安全,而是由于它能够直接操作内存和线程,如果使用不当会产生意想不到的后果,所以有必要慎用,名字能够理解为一种警示作用。
前面说到的一些线程安全类的办法进行CAS操作,便是经过调用Unsafe的办法完成的。
获取Unsafe
public final class Unsafe {
private static final Unsafe theUnsafe;
}
从上面能够看到Unsafe类中供给了一个Unsafe类型的特点theUnsafe,虽然是静态的可是权限是private,所以不能直接获取。
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
getUnsafe办法是Unsafe类中的一个静态办法,返回的便是theUnsafe特点,看到这个是不是想着直接经过Unsafe.getUnsafe()的办法就能获取Unsafe目标了。主意没错可是不要忽略这里的if条件VM.isSystemDomainLoader(),这个办法判断的是当前的类加载器是否是启动类加载器BootstrapClassLoader,而咱们自己的类使用的加载器类型是AppClassLoader,所以直接调用的话就会抛出异常。
已然不能直接调用,那咱们就想其他办法,上面也说了咱们要获取的其实便是Unsafe类的一个特点theUnsafe,获取特点值天然就想到反射了,那咱们就直接经过反射获取这个特点就好了,代码如下:
public class Test2 {
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); // 获取Unsafe类的特点字段
theUnsafe.setAccessible(true); // 由于是私有特点,所以需求敞开权限
Unsafe unsafe = (Unsafe) theUnsafe.get(null); // 获取特点的值,由于是static特点,所以传参是null
System.out.println(unsafe); // 打印成果:sun.misc.Unsafe@4f023edb
}
}
常用办法
获取特点偏移量
Unsafe类中供给了两个获取特点偏移量的办法,分别是:
-
objectFieldOffset:获取目标特点在内存中的偏移量 -
staticFieldOffset:获取静态特点在内存中的偏移量
两个办法的用法类似,这里以objectFieldOffset举例:
class Student {
volatile int age;
volatile String name;
// 省掉setter、getter
}
public class Test2 {
public static void main(String[] args) throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafe.get(null);
// 获取age、name在内存中的偏移量
long ageOffset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
long nameOffset = unsafe.objectFieldOffset(Student.class.getDeclaredField("name"));
System.out.println(ageOffset); // 12
System.out.println(nameOffset); // 16
}
}
age和name的偏移量成果分别是12和16,之前在Java并发编程之synchronized(一)一文中说到过如何打印目标信息,这里趁便验证一下两个特点的偏移量是不是正确
System.out.println(ClassLayout.parseInstance(new Student()).toPrintable());

8+4总共12个字节,实例数据部分是4+4共8个字节,另外还有4个字节填充,而age和name的内存偏移量正好便是12和16。
CAS操作
进行CAS操作的办法主要有三个:
-
compareAndSwapInt:对int类型进行CAS操作 -
compareAndSwapLong:对long类型进行CAS操作 -
compareAndSwapObject:对Object类型进行CAS操作
这三个办法的原理都是相同的,仅仅类型不同,需求留意的是前两个办法操作的类型有必要是根本类型,如果是包装类的话有必要使用第三个办法。
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
四个参数的含义分别是:要操作的目标、要操作的目标的特点的内存偏移量、特点旧值、特点新值。能够理解为把某个目标中的某个内存数据从旧值修改成新的值。
// 省掉Student类声明
public class Test {
public static void main(String[] args) throws Exception {
// 省掉获取unsafe目标
long ageOffset = unsafe.objectFieldOffset(Student.class.getDeclaredField("age"));
long nameOffset = unsafe.objectFieldOffset(Student.class.getDeclaredField("name"));
Student student = new Student();
unsafe.compareAndSwapInt(student, ageOffset, 0, 18);
unsafe.compareAndSwapObject(student, nameOffset, null, "张三");
System.out.println(student); // Student(age=18, name=张三)
}
}
上面代码分别获取了age和name特点在内存中的偏移量,然后经过unsafe的办法分别把student目标的两个特点值进行了修改,最终打印的便是修改后的成果,阐明操作就成功了。
