JavaUnsafe類與CAS操作

JavaUnsafe類與CAS操作

前言

最近看java源碼發現有多處地方都使用到了Unsafe類,於是在網上查閱資料教程.以下是個人總結

Unsafe簡介

Unsafe兩大功能:

  1. 直接通過內存地址 修改對象,獲取對象引用
  2. 使用硬件指令 實現 原子操作 (CAS compare and swap)

Unsafe的使用:

  1. Unsafe是典型的單例模式,通過 public static Unsafe getUnsafe()獲取實例

  2. 且 該方法被 @CallerSensitive所註解, 表明只能由系統類加載器加載的類所調用

  3. 爲了在測試代碼中使用Unsafe,可以通過反射獲取該類的靜態字段的實例

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

Unsafe API

獲取偏移

  1. 獲取成員變量在 對象中的偏移

    public native long objectFieldOffset(Field f);

  2. 獲取靜態成員所在 的類,返回Class對象

    public native Object staticFieldBase(Field f);

  3. 獲取靜態成員在 類中的偏移

    public native long staticFieldOffset(Field f);

  4. 獲取數組首個元素 在數組對象中的偏移

    public native int arrayBaseOffset(Class arrayClass);

  5. 獲取每個數組元素所佔空間

    public native int arrayIndexScale(Class arrayClass);

根據 對象+偏移 獲取或設置 對象中字段的引用或值

  1. 獲取 對象var1內部中偏移爲var2的 XXX類型字段的 值或引用

    public native byte getXxxx(Object var1, long var2);
    例如
       public native byte getByte(Object var1, long var2);
       public native int getInt(Object var1, long var2);
       public native double getDouble(long var1);
       public native boolean getBoolean(Object var1, long var2);
       public native Object getObject(Object var1, long var2);
    ......
    
  2. 設置對象var1內部中偏移爲var2的 XXX類型字段的值 爲var4

     public native void putBoolean(Object var1, long var2, boolean var4);
     public native void putByte(Object var1, long var2, byte var4);
     public native void putInt(Object var1, long var2, int var4);
     public native void putObject(Object var1, long var2, Object var4);
    ......
    
  3. volatile語義的get,put:表示多線程之間的變量可見,一個線程修改一個變量之後,另一個線程立刻能看到

    public native void putBooleanVolatile(Object var1, long var2, boolean var4);
    public native int getIntVolatile(Object var1, long var2);
    public native long getLongVolatile(Object var1, long var2);
    ......
    

本地內存操作

  1. 分配指定大小的一塊本地內存 (同C語言中的 malloc)

    public native long allocateMemory(long bytes);

  2. 重新分配內存(同C語言中的 realloc)

    public native long reallocateMemory(long address, long bytes);

  3. 將給定的內存塊 的所有字節 bytes 設置成固定的值 value (通過 object + offset 確定內存的基址)(同C語言中的 memset)

    public native void setMemory(Object o, long offset, long bytes, byte value);

  4. 複製內存塊,內存塊 srcBasc+srcOffset + bytes - > destBase+destOffset + bytes (同C語言中的 memcpy)

    public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset,long bytes);

  5. 釋放通過Allocate分配的本地內存(同C語言中的 free)

    public native void freeMemory(long address);

  6. 獲取和設置本地內存中的值,va1表示本地內存絕對地址,var3表示要設置的值

    public native short getShort(long var1);
    public native int getInt(long var1);
    public native void putShort(long var1, short var3);
    public native void putInt(long var1, int var3);
    

CAS操作

java.util.concurrent 包中無鎖化的實現就是調用了CAS以下原子操作

  1. CAS語義

    1. 將 由var1+var2確定的地址的值 從var4 修改成 var5
    2. 如果舊值不爲 var4,則直接退出
    3. 多個線程修改同一個變量時, 只會有一個線程修改成功,其他線程不會被掛起,而是告知失敗
    4. 這是一種 樂觀鎖的語義, 每個線程都假設自己的操作能成功,與之相對應的synchronized的悲觀鎖語義,每次修改操作必須 只能有一個線程獨佔資源
  2. 設置 通過 var1+var2確定的內存基址的int類型變量,將值原子的從 var4 變成 var5,成功true,失敗false

    替換int:public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
    替換引用:public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
    
  3. 基於上面操作的包裝方法: 得到對象 中某個int字段的值 通過(var1+var2), 並給該值加上 var4,返回相加前的值

    典型實現
    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                var5 = this.getIntVolatile(var1, var2);
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
            return var5;
        }
    

Pack/Unpack

  1. 阻塞和釋放任一線程對象

  2. 內部實現通過 信號量的方式,信號量值爲1,pack 消耗值, unpack增加值

  3. LockSupport類包裝使用

Example

//測試對象
public class UnsafeEntity {
    private  int a;
    private  int c;
    private  int d;
    private  static  int b = 1;
    getter......
    setter......
}
//測試代碼
package com.weisanju;
import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeTest {
    public static void main(String[] args) throws Exception {
        Field f= Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe u = (Unsafe) f.get(null);
        //獲取成員變量 的偏移
        long a = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("a"));
        long c = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("c"));
        long d = u.objectFieldOffset(UnsafeEntity.class.getDeclaredField("d"));
        System.out.println("成員字段a:"+a);
        System.out.println("成員字段c:"+c);
        System.out.println("成員字段d:"+d);

        //設置對象字段的值
        UnsafeEntity testa = new UnsafeEntity();
        testa.setA(666);
        System.out.println("設置前:"+u.getInt(testa, a));
        u.putInt(testa,a,777);
        System.out.println("設置後:"+u.getInt(testa, a));

        //獲取靜態字段所在的類的對象
        System.out.println(u.staticFieldBase(UnsafeEntity.class.getDeclaredField("b")));
        //獲取靜態字段的偏移
        long b = u.staticFieldOffset(UnsafeEntity.class.getDeclaredField("b"));
        System.out.println("靜態字段b:"+b);

        //靜態字段的設置, 注意由於靜態字段,存儲於方法區,所以起始對象爲類的字節碼
        System.out.println("設置前:"+u.getInt(UnsafeEntity.class, b));
        u.putInt(UnsafeEntity.class,b,11);
        System.out.println("設置後:"+u.getInt(UnsafeEntity.class, b));


        //普通 數組的使用
        int arr[] = {1,2,3,4,5,6,7,8};
        //head爲頭地址偏移
        long head = u.arrayBaseOffset(int[].class);
        //step爲數組元素所佔空間
        long step = u.arrayIndexScale(int[].class);
        // 獲取 與設置 arr[7] 的值
        int index = 7;
        System.out.println(u.getInt(arr, head + step * index));
        u.putInt(arr,head+step*index,666);
        System.out.println(arr[index]);

        //對象數組的使用
        UnsafeEntity arrObj[] = new UnsafeEntity[10];
        //head爲頭地址偏移
        head = u.arrayBaseOffset(UnsafeEntity[].class);
        //step爲數組元素所佔空間
        step = u.arrayIndexScale(UnsafeEntity[].class);
        // 獲取 與設置 arr[7] 的值
        index = 7;
        arrObj[index] = new UnsafeEntity();
        System.out.println(u.getObject(arrObj, head + step * index));
        u.putObject(arrObj,head+step*index,new UnsafeEntity());
        System.out.println(arrObj[index]);
    }
}
輸出結果
成員字段a:12
成員字段c:16
成員字段d:20
設置前:666
設置後:777
class com.weisanju.UnsafeEntity
靜態字段b:104
設置前:1
設置後:11
8
666
com.weisanju.UnsafeEntity@1540e19d
com.weisanju.UnsafeEntity@677327b6

總結

  1. Unsafe爲從cpu底層指令 層面 爲多線程提供了無鎖化設計,以及直接操作內存地址的能力,Java中 Atomic原子類,netty,concurrent包等底層都封裝了 該對象
  2. 當然 極大的效率,也必然意外着 極大的不安全, 如果錯誤給一塊內存區賦值,程序不會有任何反應,這就給程序帶來極大的安全隱患
  3. 當然瞭解Unsafe類 能夠便於我們更好的閱讀 Java底層源碼
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章