简介

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
    }
}

agename的偏移量成果分别是1216,之前在Java并发编程之synchronized(一)一文中说到过如何打印目标信息,这里趁便验证一下两个特点的偏移量是不是正确

System.out.println(ClassLayout.parseInstance(new Student()).toPrintable());

Java并发编程之Unsafe
能够看到目标头的长度是8+4总共12个字节,实例数据部分是4+48个字节,另外还有4个字节填充,而agename的内存偏移量正好便是1216

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=张三)
    }
}

上面代码分别获取了agename特点在内存中的偏移量,然后经过unsafe的办法分别把student目标的两个特点值进行了修改,最终打印的便是修改后的成果,阐明操作就成功了。