JUC基石——Unsafe類

前言

我們經常在JUC包下的ConcurrentHashMap、Atomic開頭的原子操作類、AQS以及LockSupport裏面看到Unsafe類的身影,這個Unsafe類究竟是幹什麼的,本文可以帶着讀者一探究竟。

Java和C++、C語言的一個重要區別,就是Java中我們無法直接操作一塊內存區域,而在C++、C中卻可以自己申請內存和釋放內存。Unsafe類的設計,爲我們提供了手動管理內存的能力。

如同它的名字一樣,它被認定爲不安全的。直接操縱內存,意味着實例化出來的對象不會受到JVM的管理,不會被GC,需要手動進行回收,容易出現內存泄露的問題。因此,官方並不建議我們在自己的應用程序中使用該類。


構造方法

public final class Unsafe {
    private static final Unsafe theUnsafe;

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
   //其他方法
}

可以看得出來,該類被final修飾,不允許被繼承。構造方法是私有的,在外部不可被實例化。(關於final更多的作用,可以移步這篇文章關鍵詞final的作用

        但在內部提供了一個獲取單例的getUnsafe()方法,不過該方法做了限制。如果是普通調用的話,它會拋出一個SecurityException異常。只有由系統類加載器(BootStrap classLoader)加載的類纔可以調用這個類中的方法。

如果var0由系統類加載器加載的話,那麼var0.getClassLoader()會返回null,VM.isSystemDomainLoader(null)則直接返回true,此時便不會拋出SecurityException異常。

當然,也不是無法獲取到Unsafe類的實例,我們在文章最後會通過反射來獲取。


獲取偏移量

    public native long staticFieldOffset(Field var1);

    public native long objectFieldOffset(Field var1);
  • staticFieldOffset用於獲取某一個靜態屬性在對象地址中的偏移量
  • objectFieldOffset用於獲取某一個非靜態屬性在對象實例地址中的偏移量

偏移量這個名詞在Unsafe類中十分重要,該類中80%的方法都需要依賴這個偏移量。


分配、釋放內存等

    
    //分配內存
    public native long allocateMemory(long var1);

    //擴展或重新分配內存
    public native long reallocateMemory(long var1, long var3);

    //內存初始化
    public native void setMemory(Object var1, long var2, long var4, byte var6);

    //內存複製
    public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);

    //釋放內存
    public native void freeMemory(long var1);

    //創建對象實例並返回該實例
    public native Object allocateInstance(Class<?> var1) throws InstantiationException;

其中allocateMemory()、reallocateMemory()、freeMemory()分別用於分配內存,擴展或重新分配內存和釋放內存,與C語言中 malloc()、realloc()、free()對應。

allocateInstance()方法會通過Class對象創建一個類的實例,且不需要調用其構造函數、初始化代碼、JVM安全檢查等等。


普通讀寫

    public native int getInt(long var1);

    public native void putInt(long var1, int var3);
  • getInt()   在指定內存地址var1處讀取一個int
  • putInt()   在指定內存地址var1處寫入一個新int類型的數據var3

普通的讀寫,無法保證有序性與可見性。關於可見性,可以先移步到我的另外一篇文章多線程之內存可見性


volatile讀寫

    public native int getIntVolatile(Object var1, long var2);

    public native void putIntVolatile(Object var1, long var2, int var4);
  • getIntVolatile   在對象var1的指定偏移處var2讀取一個int
  • putIntVolatie    在對象var1的指定偏移處var2寫入一個int類型的數據var4

volatie能夠保證有序性以及可見性,volatie保證有序性的一個實例,可以參考我的另外一篇文章淺說Synchronized中使用synchronized與volatie實現單例模式中雙重檢驗鎖的部分。


CAS操作

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

以compareAndSwapInt爲例,在對象var1指定偏移量var2處讀取一個int值real,如果var4=real,則用var5更新這個real,並返回true,否則返回false。

CAS用於實現樂觀鎖,每次更新時,都假設不會有其他線程併發修改,而只是在修改的時候判斷該值是否被修改過。如果符合預期的值,則直接更新它,否則進入忙循環中,一直判斷是否符合期望。在循環次數少便可以直接更新值的情況下,CAS機制比悲觀鎖擁有更好的性能。當然,如果循環次數過多,也是會白白浪費CPU資源。

synchronized就是一中悲觀鎖的實現,關於synchronized原理,可以移步我的另外一篇文章Synchronized的優化

關於CAS機制的詳細介紹,我會另開篇幅。


線程調度

    public native void park(boolean var1, long var2);
 
    public native void unpark(Object var1);

  • park()        用於阻塞當前線程,如果var1=true,則var2的單位爲毫秒,否則爲納秒。
  • unpark()    用於恢復一個之前被park的線程var1

LockSupport裏面的park()與unpark()方法內部正是通過以上兩個本地方法來實現的。


內存屏障

    public native void loadFence();

    public native void storeFence();

    public native void fullFence();
  • loadFence      保證在這個屏障之前,所有的讀操作全部完成。這裏的load對應於從局部變量表中讀取某個位置上的元素到棧頂
  • storeFence     保證在這個屏障之前,所有的寫操作全部完成。這裏的store對應於從棧頂出棧某個元素保存到局部變量表的某個位置上
  • fullFence        保證在這個屏障之前,所有的讀寫操作全部完成,相當於load+store

反射獲取Unsafe實例

package com.yang.testUnsafe;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Main {

    static class Student {
        private String name;
        private int age;

        public Student() {
            System.out.println("通過構造方法");
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
        //1.獲取Unsafe類的實例
        //但無法通過Unsafe.getUnsafe(),由於Main類不是系統類加載器加載,因此會拋出SecurityException異常
        //Unsafe unsafe = Unsafe.getUnsafe();
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        //2.爲Student對象分配內存
        //該方法不需要調用構造方法
        Student student = (Student) unsafe.allocateInstance(Student.class);

        //3.獲取student實例中age屬性的偏移量
        Field age = student.getClass().getDeclaredField("age");
        long ageOffset = unsafe.objectFieldOffset(age);

        //4.利用CAS操作,當age=0時,將age變爲1
        boolean casResult = unsafe.compareAndSwapInt(student, ageOffset, 0, 1);
        System.out.println(casResult);//輸出true
        System.out.println(student.getAge());//輸出1
    }
}

總結

使用Unsafe類直接操縱內存,意味着速度更快,效率更高,但也更加危險。之前盛傳Unsafe類將在Java9中移除,一時間風波四起,具體的文章可以參考這篇Java 9中將移除 Sun.misc.Unsafe(譯)

但其實Java9出現之後,只是對其進行了改進和優化,不過依然是不推薦開發者使用Unsafe類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章