JavaUnsafe類與CAS操作
前言
最近看java源碼發現有多處地方都使用到了Unsafe類,於是在網上查閱資料教程.以下是個人總結
Unsafe簡介
Unsafe兩大功能:
- 直接通過內存地址 修改對象,獲取對象引用
- 使用硬件指令 實現 原子操作 (CAS compare and swap)
Unsafe的使用:
-
Unsafe是典型的單例模式,通過
public static Unsafe getUnsafe()
獲取實例 -
且 該方法被
@CallerSensitive
所註解, 表明只能由系統類加載器加載的類所調用 -
爲了在測試代碼中使用Unsafe,可以通過反射獲取該類的靜態字段的實例
Field f= Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe u = (Unsafe) f.get(null);
Unsafe API
獲取偏移
-
獲取成員變量在 對象中的偏移
public native long objectFieldOffset(Field f);
-
獲取靜態成員所在 的類,返回
Class
對象public native Object staticFieldBase(Field f);
-
獲取靜態成員在 類中的偏移
public native long staticFieldOffset(Field f);
-
獲取數組首個元素 在數組對象中的偏移
public native int arrayBaseOffset(Class arrayClass);
-
獲取每個數組元素所佔空間
public native int arrayIndexScale(Class arrayClass);
根據 對象+偏移 獲取或設置 對象中字段的引用或值
-
獲取 對象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); ......
-
設置對象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); ......
-
帶
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); ......
本地內存操作
-
分配指定大小的一塊本地內存 (同C語言中的 malloc)
public native long allocateMemory(long bytes);
-
重新分配內存(同C語言中的 realloc)
public native long reallocateMemory(long address, long bytes);
-
將給定的內存塊 的所有字節
bytes
設置成固定的值value
(通過object + offset
確定內存的基址)(同C語言中的 memset)public native void setMemory(Object o, long offset, long bytes, byte value);
-
複製內存塊,
內存塊 srcBasc+srcOffset + bytes - > destBase+destOffset + bytes
(同C語言中的 memcpy)public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset,long bytes);
-
釋放通過Allocate分配的本地內存(同C語言中的 free)
public native void freeMemory(long address);
-
獲取和設置本地內存中的值,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以下原子操作
-
CAS語義
- 將 由var1+var2確定的地址的值 從var4 修改成 var5
- 如果舊值不爲 var4,則直接退出
- 多個線程修改同一個變量時, 只會有一個線程修改成功,其他線程不會被掛起,而是告知失敗
- 這是一種 樂觀鎖的語義, 每個線程都假設自己的操作能成功,與之相對應的synchronized的悲觀鎖語義,每次修改操作必須 只能有一個線程獨佔資源
-
設置 通過 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);
-
基於上面操作的包裝方法: 得到對象 中某個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,pack 消耗值, unpack增加值
-
在
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
總結
- Unsafe爲從cpu底層指令 層面 爲多線程提供了無鎖化設計,以及直接操作內存地址的能力,Java中 Atomic原子類,netty,concurrent包等底層都封裝了 該對象
- 當然 極大的效率,也必然意外着 極大的不安全, 如果錯誤給一塊內存區賦值,程序不會有任何反應,這就給程序帶來極大的安全隱患
- 當然瞭解Unsafe類 能夠便於我們更好的閱讀 Java底層源碼