- Unsafe類 不能直接 new, 其構(gòu)造函數(shù)被私有化
-
public final class Unsafe
: Unsafe 是 final 的, 不能被繼承 - 方法:
- native 的 compareAndSwap 方法, 實現(xiàn) CAS 比較交換
- native 的 put/get volatile 類型變量
- native 的 park/unpark ,實現(xiàn)加鎖解鎖
- java 本地方法的 getAndSet 和 getAndAdd, 內(nèi)部調(diào)用了 native 的 compareAndSwap
Unsafe 之所以叫做不安全的, 是因為他可以直接操作內(nèi)存地址, 修改對象屬性
-
Unsafe 暴露了一個靜態(tài)方法用于獲取對象, 但會校驗加載 Unsafe 的是否為
Bootstrap ClassLoader
, 不是則拋異常public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return this.theUnsafe; } } // 靜態(tài)代碼塊 static { registerNatives(); Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"}); theUnsafe = new Unsafe(); ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class); ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class); ... ... ... ADDRESS_SIZE = theUnsafe.addressSize(); }
因此, 如果想獲取 Unsafe 實例, 只能通過反射. 看到 getUnsafe
返回 theUnsafe 屬性, 且改屬性在靜態(tài)代碼塊中已被初始化. 即只要該類加載, 該類的實例就被存在屬性中, 我們直接反射獲取屬性即可
public static Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
//Field unsafeField = Unsafe.class.getDeclaredFields()[0]; //也可以這樣砂蔽,作用相同
unsafeField.setAccessible(true);
Unsafe unsafe =(Unsafe) unsafeField.get(null);
return unsafe;
}
二. 功能實現(xiàn)
-
內(nèi)存操作
實現(xiàn)像 C++ 一樣的申請, 釋放, 調(diào)整, 拷貝內(nèi)存. 通過 Unsafe 申請的內(nèi)存是對外內(nèi)存, 需要手動釋放. 方法為在 finally 中釋放申請的內(nèi)存- 方法列表
// 分配新的本地空間 public native long allocateMemory(long bytes); // 重新調(diào)整內(nèi)存空間的大小 public native long reallocateMemory(long address, long bytes); // 將內(nèi)存設(shè)置為指定值 public native void setMemory(Object o, long offset, long bytes, byte value); // 內(nèi)存拷貝 public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes); // 清除內(nèi)存 public native void freeMemory(long address);
- 示例
private static void memoryTest(Unsafe unsafe) { int size = 4; // 分配4字節(jié)長度的內(nèi)存 long addr = unsafe.allocateMemory(size); // 分配一個8字節(jié)長度的內(nèi)存 long addr3 = unsafe.reallocateMemory(addr, size * 2); System.out.println("addr地址: "+addr); System.out.println("addr3地址: "+addr3); try { // 初始化內(nèi)存, 向4個字節(jié)中的每個字節(jié), 一次寫入 byte(1). 即內(nèi)存中為 0001 0001 0001 0001 => 組成的4字節(jié) int 型值為 16843009 unsafe.setMemory(null,addr ,size,(byte)1); // 從 addr 想 addr3 拷貝值, 因為 addr3 比 addr 長1倍, 所以需要拷貝2次 // 拷貝后, addr3 中的內(nèi)容為 0001 0001 0001 0001 0001 0001 0001 0001 => long 型值為 72340172838076673 for (int i = 0; i < 2; i++) { unsafe.copyMemory(null,addr,null,addr3+size*i,4); } System.out.println(unsafe.getInt(addr)); System.out.println(unsafe.getLong(addr3)); }finally { // 手動釋放內(nèi)存, Unsafe的內(nèi)存不在 jvm 中 unsafe.freeMemory(addr); unsafe.freeMemory(addr3); } }
-
內(nèi)存屏障
- Unsafe 提供了防止指令重排的方法. 分為讀重排, 寫重排, 讀寫重排. 內(nèi)存屏障相當于一個操作的同步點, 確保同步點之前的指令不會被重排到同步點之后
public native void loadFence(); public native void storeFence(); public native void fullFence();
- 以
loadFence
為例, 他會禁止讀操作重排序, 確保該點之前的所有讀操作都已完成, 且會將線程從主內(nèi)存拷貝過來的緩存值設(shè)為無效, 重新從主內(nèi)存中加載數(shù)據(jù) - 示例, 我們用 Unsafe 的讀屏障, 模擬 volatile
// 定義一個線程方法,在線程中去修改flag標志位,注意這里的flag是沒有被volatile修飾的: class ChangeThread implements Runnable{ /**volatile**/ boolean flag=false; @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("subThread change flag to:" + flag); flag = true; } } // 主線程用讀屏障, 確保變量的可見性 public static void main(String[] args){ ChangeThread changeThread = new ChangeThread(); new Thread(changeThread).start(); while (true) { boolean flag = changeThread.isFlag(); unsafe.loadFence(); //加入讀內(nèi)存屏障 if (flag){ System.out.println("detected flag changed"); break; } } System.out.println("main thread end"); }
-
對象操作
-
修改屬性值
- Unsafe 可以通過計算某個屬性在對象內(nèi)中的偏移量, 直接修改內(nèi)存地址上的值, 達到修改對象屬性的目的
/** * 獲取屬性的內(nèi)存地址修改對象屬性值 */ public static void fieldTest(Unsafe unsafe) throws NoSuchFieldException { User user=new User(); long fieldOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age")); System.out.println("內(nèi)存地址offset:"+fieldOffset); unsafe.putInt(user,fieldOffset,20); System.out.println("age:"+unsafe.getInt(user,fieldOffset)); System.out.println("age:"+user.getAge()); }
- Unsafe 的操作屬性修改有三類: volatile讀寫, 有序?qū)懭? 普通讀寫
// 在對象的指定偏移地址獲取一個對象引用 // (1) 普通讀寫 public native Object getObject(Object o, long offset); // 在對象指定偏移地址寫入一個對象引用 public native void putObject(Object o, long offset, Object x); // (2) volatile 讀寫 //在對象的指定偏移地址處讀取一個int值,支持volatile load語義 public native int getIntVolatile(Object o, long offset); //在對象指定偏移地址處寫入一個int菱蔬,支持volatile store語義 public native void putIntVolatile(Object o, long offset, int x); // (3) 順序讀寫 public native void putOrderedObject(Object o, long offset, Object x); public native void putOrderedInt(Object o, long offset, int x); public native void putOrderedLong(Object o, long offset, long x);
- 從實現(xiàn)效果上看
- 有序?qū)懭? 保證寫入時的有序性夹攒,而不保證可見性,也就是一個線程寫入的值不能保證其他線程立即可見碟绑。
- volatile寫入: 保證可見性和有序性单默。get 和 set 會同步操作主存. 在執(zhí)行g(shù)et操作時碘举,強制從主存中獲取屬性值; 在使用put方法設(shè)置屬性值時,會強制將值更新到主存中搁廓,從而保證這些變更對其他線程是可見的引颈。
-
初始化對象
- Unsafe 的
allocateInstance
方法,允許我們使用非常規(guī)的方式進行對象的實例化. 不通過構(gòu)造函數(shù)初始化對象, 通過對象及其屬性的字段長度初始化 - Unsafe 的
allocateInstance
初始化對象, 只能將屬性設(shè)置為每種數(shù)據(jù)類型的默認值
public class A { private int b; public A(){ this.b =1; } } public void objTest() throws Exception{ A a1=new A(); System.out.println(a1.getB()); A a2 = A.class.newInstance(); System.out.println(a2.getB()); A a3= (A) unsafe.allocateInstance(A.class); System.out.println(a3.getB()); }
- Unsafe 的
-
數(shù)組操作 (略)
-
CAS
在 Unsafe 類中境蜕,提供了compareAndSwapObject
蝙场、compareAndSwapInt
、compareAndSwapLong
方法來實現(xiàn)的對 Object粱年、int售滤、long 類型的CAS操作public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
-
compareAndSwapInt
方法名: 表示 CAS 更新對象中的 int 屬性 -
Object o
: 待更新的對象 -
long offset
: 待更新字段的地址偏移量 -
int expected,int x
: 表示只有字段值為 expected 時, 才把字段值改為 x
如下示例 , 輸出 1 2 3 4 5 6 7 8 9
public class Test { private volatile int a; public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Test Test=new Test(); Unsafe unsafe = Test.getUnsafe(); // 負責(zé) [1,5) 的累加 new Thread(()->{ for (int i = 1; i < 5; i++) { Test.increment(i,unsafe); System.out.print(Test.a+" "); } }).start(); // 負責(zé) [5,10] 的累加 new Thread(()->{ for (int i = 5 ; i <10 ; i++) { Test.increment(i,unsafe); System.out.print(Test.a+" "); } }).start(); } private void increment(int x, Unsafe unsafe){ while (true){ try { // 只有在a的值等于傳入的參數(shù)x減一時,才會將a的值變?yōu)閤 long fieldOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("a")); if (unsafe.compareAndSwapInt(this,fieldOffset,x-1,x)) break; else Thread.yield(); } catch (NoSuchFieldException e) { e.printStackTrace(); } } } }
-
-
線程調(diào)度
AQS 經(jīng)常使用LockSupport
掛起或喚醒指定線程. LockSupport 的park
方法調(diào)用了Unsafe 的 park 方法來阻塞當前線程台诗,此方法將線程阻塞后就不會繼續(xù)往后執(zhí)行完箩,直到有其他線程調(diào)用unpark
方法喚醒當前線程。-
unpark
方法是其他線程調(diào)用, 喚醒本線程的 -
park
方法是本線程調(diào)用, 自己阻塞自己
下面一個例子對Unsafe的這兩個方法進行測試:
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Thread mainThread = Thread.currentThread(); Unsafe unsafe = Test.getUnsafe(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(5); System.out.println("subThread try to unpark mainThread"); // 新線程喚醒主線程 unsafe.unpark(mainThread); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); System.out.println("park main mainThread"); // 主線程自己阻塞自己 unsafe.park(false,0L); System.out.println("unpark mainThread success"); }
-
-
對 Class 操作
Unsafe 對 Class 的相關(guān)操作主要包括類加載和靜態(tài)變量的操作方法拉队。- 加載靜態(tài)變量
- 前面提到 unsafe 可以操作對象的屬性, 但靜態(tài)屬性和對象沒關(guān)系, 和類有關(guān)系; 靜態(tài)屬性單獨存在方法區(qū), jdk1.8 后靜態(tài)屬性存在堆中, 但仍與普通對象在不同區(qū)域. 因此操作靜態(tài)屬性的是新的 api
//獲取靜態(tài)屬性的偏移量 public native long staticFieldOffset(Field f); //獲取靜態(tài)屬性的對象指針 public native Object staticFieldBase(Field f); //判斷類是否需要實例化(用于獲取類的靜態(tài)屬性前進行檢測) // 如果 jvm 已加載 Class c , 則返回 false, 否則返回 true public native boolean shouldBeInitialized(Class<?> c);
- 測試示例
上面示輸出 false, Hydraprivate static void staticTest(Unsafe unsafe) throws Exception { User user = new User(); // 為了讓 jvm 加載類, 從而初始化靜態(tài)字段. 下面那句和這句目的一樣 // Class.forName(User.class.getName()); System.out.println(unsafe.shouldBeInitialized(User.class)); // 反射字段 Field sexField = User.class.getDeclaredField("name"); // 獲取靜態(tài)字段 "name" 對象 Object fieldBase = unsafe.staticFieldBase(sexField); // 獲取靜態(tài)字段 "name" 所在方法區(qū)/堆 中的地址 long fieldOffset = unsafe.staticFieldOffset(sexField); Object object = unsafe.getObject(fieldBase, fieldOffset); System.out.println(object); } class User { public static String name = "Hydra"; int age; }
如果注釋掉User user = new User();
, 輸出 true , null
- 加載類
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader,ProtectionDomain protectionDomain);
- 加載靜態(tài)變量
-
系統(tǒng)信息
Unsafe 中提供的addressSize
和pageSize
方法用于獲取系統(tǒng)信息-
addressSize
方法會返回系統(tǒng)指針的大小弊知,如果在64位系統(tǒng)下默認會返回8,而32位系統(tǒng)則會返回4粱快。 -
pageSize
方法會返回內(nèi)存頁的大小秩彤,值為2的整數(shù)冪夺鲜。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Unsafe unsafe = Test.getUnsafe(); System.out.println(unsafe.addressSize()); // 8 (64位系統(tǒng), 地址長度為8字節(jié); 32位系統(tǒng), 地址長度為4字節(jié)) System.out.println(unsafe.pageSize()); // 4 (內(nèi)存頁大小: 4kb) }
-