Unsafe

1. 介绍

Unsafe顾名思义它是一个“不安全”的类,但是仍有很多框架(比如:Netty、Hadoop、Kafka等)喜欢使用它,为什么呢?因为它是直接和系统打交道,执行效率会比较高。但是它的很多方法都是很偏向底层的,一般人使用可能会对系统造成不好的影响,所以java官方不推荐使用这个类。但是不影响我们分析这个类,并且它也是分析并发的基础。

2. 源码分析

public final class Unsafe {

    (1) 获取实例(单例)
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

    // 以address为内存的起始地址获取int值(还包含其他基本类型)
    public native int     getInt(long address);
	// 以address为内存的起始地址设置int值(还包含其他基本类型)
    public native void    putInt(long address, int x);

	// 设置和获取直接内存地址
    public native long getAddress(long address);
    public native void putAddress(long address, long x);

    // 分配内存
    public native long allocateMemory(long bytes);
    // 重新分配内存
    public native long reallocateMemory(long address, long bytes);

    // 拷贝内存
    public native void copyMemory(Object srcBase, long srcOffset,
                                  Object destBase, long destOffset,
                                  long bytes);
    // 释放内存(使用allocateMemory分配内存之后记得使用该方法释放)
    public native void freeMemory(long address);

    // 获取类属性的偏移量
    public native long staticFieldOffset(Field f);
	// 获取对象属性的偏移量
    public native long objectFieldOffset(Field f);
	// 该类属性所属类
    public native Object staticFieldBase(Field f);
	// 判断内存中是否存在c对应的实例对象
    public native boolean shouldBeInitialized(Class<?> c);
	// 确保内存中存在该实例(自动创建)
    public native void ensureClassInitialized(Class<?> c);
    // 返回数组中第一个元素的偏移地址
    public native int arrayBaseOffset(Class<?> arrayClass);
    // 数组中每个元素的增量地址,可以通过 arrayBaseOffset和arrayIndexScale确定所有的数组元素
    public native int arrayIndexScale(Class<?> arrayClass);
    // 获取本机内存页大小,这个值永远都是2的幂次方  
    public native int pageSize();
    // 定义一个类
    public native Class<?> defineClass(String name, byte[] b, int off, int len,
                                       ClassLoader loader,
                                       ProtectionDomain protectionDomain);
    // 定义一个匿名类
    public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
    // 创建一个实例对象
    public native Object allocateInstance(Class<?> cls)
        throws InstantiationException;

    // 抛出异常
    public native void throwException(Throwable ee);
	
	(2) CAS
    // 如果对象偏移量上的值=期待值,更新为x,返回true.否则false.类似的有compareAndSwapInt,compareAndSwapLong,compareAndSwapBoolean,compareAndSwapChar等等。
    public final native boolean compareAndSwapObject(Object o, long offset,
                                                     Object expected,
                                                     Object x);
   // 设置对象o指定偏移量offset的值为x,使用volatile语义。 (3) volatile语义
    public native void    putIntVolatile(Object o, long offset, int x);
    // 设置对象o指定偏移量offset的值为x,使用volatile语义并带有版本
    public native void    putOrderedInt(Object o, long offset, int x);

    // 终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的
    public native void unpark(Object thread);

    //线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现
    public native void park(boolean isAbsolute, long time);

    // 原子性添加
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, v + delta));
        return v;
    }
     // 原子性修改
    public final int getAndSetInt(Object o, long offset, int newValue) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!compareAndSwapInt(o, offset, v, newValue));
        return v;
    }

	(4)内存屏障
   // 禁止load操作重排序
    public native void loadFence();

   //禁止store操作重排序
    public native void storeFence();

    //禁止load、store操作重排序
    public native void fullFence();

}

(1) 获取Unsafe实例
只有jdk自身调用是合法的,否则会抛出SecurityException。所以为了获取该实例需要使用下面的方式

Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);

(2) CAS

CAS全称“Compare and Swap”即比较完成之后再交换,它包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
正常情况下,如果存在多个线程访问肯定会存在并发问题,可以使用同步锁来实现每次只有一个线程操作,但是这样的性能是非常低的,CAS可以将这些复杂过程变成原子性操作。
(3) volatile语义
  一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2)禁止进行指令重排序。
(4) 内存屏障

由于现代的操作系统都是多处理器.而每一个处理器都有自己的缓存,并且这些缓存并不是实时都与内存发生信息交换.这样就可能出现一个cpu上的缓存数据与另一个cpu上的缓存数据不一致的问题.而这样在多线程开发中,就有可能导致出现一些异常行为.
而操作系统底层为了这些问题,提供了一些内存屏障用以解决这样的问题.目前有4种屏障.

LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能。

3. 方法分类:

一、内存管理。包括分配内存、释放内存等。

该部分包括了allocateMemory(分配内存)、reallocateMemory(重新分配内存)、copyMemory(拷贝内存)、freeMemory(释放内存 )、getAddress(获取内存地址)、addressSize、pageSize、getInt(获取内存地址指向的整数)、getIntVolatile(获取内存地址指向的整数,并支持volatile语义)、putInt(将整数写入指定内存地址)、putIntVolatile(将整数写入指定内存地址,并支持volatile语义)、putOrderedInt(将整数写入指定内存地址、有序或者有延迟的方法)等方法。getXXX和putXXX包含了各种基本类型的操作。

利用copyMemory方法,我们可以实现一个通用的对象拷贝方法,无需再对每一个对象都实现clone方法,当然这通用的方法只能做到对象浅拷贝。

二、非常规的对象实例化。

allocateInstance()方法提供了另一种创建实例的途径。通常我们可以用new或者反射来实例化对象,使用allocateInstance()方法可以直接生成对象实例,且无需调用构造方法和其它初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

三、操作类、对象、变量。

这部分包括了staticFieldOffset(静态域偏移)、defineClass(定义类)、defineAnonymousClass(定义匿名类)、ensureClassInitialized(确保类初始化)、objectFieldOffset(对象域偏移)等方法。

通过这些方法我们可以获取对象的指针,通过对指针进行偏移,我们不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。

四、数组操作。

这部分包括了arrayBaseOffset(获取数组第一个元素的偏移地址)、arrayIndexScale(获取数组中元素的增量地址)等方法。arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

五、多线程同步。包括锁机制、CAS操作等。

这部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。

其中monitorEnter、tryMonitorEnter、monitorExit已经被标记为deprecated,不建议使用。

Unsafe类的CAS操作可能是用的最多的,它为Java的锁机制提供了一种新的解决办法,比如AtomicInteger等类都是通过该方法来实现的。compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率。这是一种乐观锁,通常认为在大部分情况下不出现竞态条件,如果操作失败,会不断重试直到成功。

六、挂起与恢复。

这部分包括了park、unpark等方法。

将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

七、内存屏障。

这部分包括了loadFence、storeFence、fullFence等方法。这是在Java 8新引入的,用于定义内存屏障,避免代码重排序。

loadFence() 表示该方法之前的所有load操作在内存屏障之前完成。同理storeFence()表示该方法之前的所有store操作在内存屏障之前完成。fullFence()表示该方法之前的所有load、store操作在内存屏障之前完成。

4. 功能测试

public class UnsafeTest {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException, InstantiationException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.println("地址大小:" + unsafe.addressSize());

        // --------内存管理--------
        // 分配4字节内存空间
        long addr = unsafe.allocateMemory(4);
        System.out.println("申请内存地址:" + addr + "成功");
        System.out.println("释放内存前,地址" + addr + "存储的数据:" + unsafe.getInt(addr));
        unsafe.putInt(addr, 95);
        System.out.println("释放内存前,修改地址" + addr + "存储数据之后:" + unsafe.getInt(addr));
        unsafe.freeMemory(addr);
        System.out.println("释放内存地址:" + addr + "成功");
        System.out.println("释放内存后,地址" + addr + "存储的数据:" + unsafe.getInt(addr));

//        long address = unsafe.getAddress(addr);
        long addr2 = unsafe.allocateMemory(8);
        // 获取直接内存地址
        System.out.println(unsafe.getAddress(addr2));

        // 操作类
        Field countyField = Student.class.getDeclaredField("COUNTY");
        long countyFieldOffset = unsafe.staticFieldOffset(countyField);
        System.out.println(countyFieldOffset);
        Field nameField = Student.class.getDeclaredField("name");
        System.out.println(unsafe.fieldOffset(nameField));
        Object o = unsafe.staticFieldBase(countyField);
        System.out.println(o);

        // 检查内存中是否存在该实例
        System.out.println(unsafe.shouldBeInitialized(Student.class));
        Student student = new Student("zhangsan",12);
        System.out.println(unsafe.shouldBeInitialized(Student.class));

        // 效果等同于下面
        System.out.println(unsafe.shouldBeInitialized(Student.class));
        unsafe.ensureClassInitialized(Student.class);
        System.out.println(unsafe.shouldBeInitialized(Student.class));

       // 创建一个实例对象(不通过构造器,属性使用默认值)
        Student stu1 = (Student)unsafe.allocateInstance(Student.class);
        System.out.println(stu1);
        System.out.println(stu1.getAge());
        System.out.println(stu1.getName());
    }


}

class Student {
    private static final String COUNTY = "CN";
    private String name;
    private int age;

    Student(String name, int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

5. 参考资料

  1. Java内存屏障和可见性
  2. Java并发与锁设计实现详述(6)- 聊一聊Unsafe
  3. Java并发编程:volatile关键字解析

你可能感兴趣的:(并发编程,并发,Unsafe,内存屏障,volatile)