JDK原始碼(一)Unsafe

吳小爽發表於2018-02-12

1.JAVA提供的操作記憶體類

    sun.misc.Unsafe 操作類
複製程式碼

此類在提升Java執行效率,很多Java基礎類庫都使用了此類以提升效能。
此類使Java擁有了像C語言的指標一樣操作記憶體空間的能力,但是也使程式易錯。

2.程式碼片段

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if(var0.getClassLoader() != null) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
複製程式碼

getUnsafe()方法只能由ClassLoader == null的類呼叫,意味著呼叫這個方法的類是jdk中C程式碼載入的。

3.可以使用的方式

    public static Unsafe getUnsafe() {
        try {
            final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
            unsafeField.setAccessible(true);
            // the unsafe instance
            return (Unsafe) unsafeField.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
複製程式碼

使用反射可以獲取到Unsafe操作類例項。

4.Unsafe基本操作

  • 操作成員變數
    public class Dog {
        private String name;
        private int age;
        private long size;
    }
複製程式碼
    public static void main(String[] args) throws Exception{
        Unsafe unsafe = getUnsafe();
        Dog dog = new Dog();
        // 獲取name屬性偏移量
        long nameOffset = unsafe.objectFieldOffset(Dog.class.getDeclaredField("name"));
        unsafe.putObject(dog, nameOffset, "Rick");
        // 獲取age屬性偏移量
        long ageOffset = unsafe.objectFieldOffset(Dog.class.getDeclaredField("age"));
        unsafe.putInt(dog, ageOffset, 11);
        // 獲取size屬性偏移量
        long sizeOffset = unsafe.objectFieldOffset(Dog.class.getDeclaredField("size"));
        unsafe.putLong(dog, sizeOffset, 1000L);
        System.out.println(dog);
    }
複製程式碼
  • 運算元組
    @Test
    public void testArray(){
        Unsafe unsafe = UnsafeMain.getUnsafe();
        // pigs[]陣列基本偏移量
        int base = unsafe.arrayBaseOffset(Pig[].class);
        // pigs[]陣列的單位偏移量
        int indexScale = unsafe.arrayIndexScale(Pig[].class);
        // unsafe運算元組
        Pig[] pigs = new Pig[8];
        Pig pig = new Pig("Rick", 10, 800L);
        unsafe.compareAndSwapObject(pigs, base + 5 * indexScale, null, pig);
        System.out.println(pigs[5]);
    }
複製程式碼
  • 記憶體操作
    @Test
    public void testMemory(){
        Unsafe unsafe = UnsafeMain.getUnsafe();
        // 申請記憶體
        long index = unsafe.allocateMemory(1L);
        // 設定記憶體
        unsafe.putByte(index, (byte)-127);
        // 根據地址獲取資料
        System.out.println(unsafe.getByte(index));
        // 釋放記憶體
        unsafe.freeMemory(index);
    }
複製程式碼
  • 多執行緒同步(包括鎖機制,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操作在記憶體屏障之前完成。

5.總結

一些JDK原始碼中使用了此Unsafe,如ConcurrentHashMap等。為之後的分析做準備。

相關文章