简介
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
目标的两个特点值进行了修改,最终打印的便是修改后的成果,阐明操作就成功了。