日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JAVA Unsafe类

發布時間:2025/3/15 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JAVA Unsafe类 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應該被普通用戶使用。但是,為了更好地了解java的生態體系,我們應該去學習它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。

一、獲取Unsafe的實例

查看Unsafe的源碼我們會發現它提供了一個getUnsafe()的靜態方法。

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

但是,如果直接調用這個方法會拋出一個SecurityException異常,這是因為Unsafe僅供java內部類使用,外部類不應該使用它。

public class GetUnsafe {public static void main(String[] args) {Unsafe unsafe = Unsafe.getUnsafe();System.out.println(unsafe);} }


那么,如何獲取該類實例?反射!,查看源碼我們發現它有一個屬性叫theUnsafe,我們直接通過反射拿到它即可。

public class GetUnsafeByReflect {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);System.out.println(unsafe);} }

二、Unsafe類作用

那么問題來了,拼命拿到這個類有啥用?

  • 使用Unsafe實例化一個類
    • 假如我們有一個簡單的類如下
    class TestUnsafe {int age;public TestUnsafe() {this.age = 10;} }
    • 實例化該類
    @Testpublic void getClassEntityByNormalMethod(){//通過構造方法實例化這個類,age屬性將會返回10TestUnsafe testUnsafe = new TestUnsafe();System.out.println(testUnsafe.age);}@Testpublic void getClassEntityByUnsafe() throws NoSuchFieldException, IllegalAccessException, InstantiationException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);TestUnsafe testUnsafe = (TestUnsafe)unsafe.allocateInstance(TestUnsafe.class);//因為unsafe.allocateInstance()只會給對象分配內存,并不會調用構造方法,所以這里只會返回int類型的默認值0。System.out.println(testUnsafe.age);}
  • 修改私有字段的值
  • 是的,沒看錯,使用unsafe,可以突破任何修飾符的限制,直接修改字段屬性。(雖然通過原來的類經過反射也能修改private屬性,但是unsafe顯然簡單得多)

    • 使用Unsafe的putXXX()方法,我們可以修改任意私有字段的值。
    class TestUnsafe {private final int age;public int getAge() {return age;}public TestUnsafe() {this.age = 10;} } --------------------------------------------------------@Testpublic void modifyPrivateAttr() throws NoSuchFieldException, IllegalAccessException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);TestUnsafe testUnsafe = new TestUnsafe();Field age = testUnsafe.getClass().getDeclaredField("age");unsafe.putInt(testUnsafe, unsafe.objectFieldOffset(age), 20);// 打印20System.out.println(testUnsafe.getAge());}
  • 拋出checked異常
  • 我們知道如果代碼拋出了checked異常,要不就使用try…catch捕獲它,要不就在方法簽名上定義這個異常,但是,通過Unsafe我們可以拋出一個checked異常,同時卻不用捕獲或在方法簽名上定義它。

    @Testpublic void throwNormalException() throws IOException {// 使用正常方式拋出IOException需要定義在方法簽名上往外拋throw new IOException();} ----------------------------------------------------------@Testpublic void throwExceptionByUnsafe() throws NoSuchFieldException, IllegalAccessException {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);// 使用Unsafe拋出異常不需要定義在方法簽名上往外拋unsafe.throwException(new IOException());}
  • 內存操作
  • 這部分主要包含堆外內存的分配、拷貝、釋放、給定地址值操作等方法。

    //分配內存, 相當于C++的malloc函數 public native long allocateMemory(long bytes); //擴充內存 public native long reallocateMemory(long address, long bytes); //釋放內存 public native void freeMemory(long address); //在給定的內存塊中設置值 public native void setMemory(Object o, long offset, long bytes, byte value); //內存拷貝 public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); //獲取給定地址值,忽略修飾限定符的訪問限制。與此類似操作還有: getInt,getDouble,getLong,getChar等 public native Object getObject(Object o, long offset); //為給定地址設置值,忽略修飾限定符的訪問限制,與此類似操作還有: putInt,putDouble,putLong,putChar等 public native void putObject(Object o, long offset, Object x); //獲取給定地址的byte類型的值(當且僅當該內存地址為allocateMemory分配時,此方法結果為確定的) public native byte getByte(long address); //為給定地址設置byte類型的值(當且僅當該內存地址為allocateMemory分配時,此方法結果才是確定的) public native void putByte(long address, byte x);
    • 以使用堆外內存為例

    如果進程在運行過程中JVM上的內存不足了,會導致頻繁的進行GC。理想情況下,我們可以考慮使用堆外內存,這是一塊不受JVM管理的內存。使用Unsafe的allocateMemory()我們可以直接在堆外分配內存,這可能非常有用,但我們要記住,這個內存不受JVM管理,因此我們要調用freeMemory()方法手動釋放它。假設我們要在堆外創建一個巨大的int數組,我們可以使用allocateMemory()方法來實現:

    • 堆外數組類
    class OffHeapArray {// 一個int等于4個字節private static final int INT = 4;private final long size;private final long address;private static Unsafe unsafe;static {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);unsafe = (Unsafe) f.get(null);} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}// 構造方法,分配內存public OffHeapArray(long size) {this.size = size;// 參數字節數address = unsafe.allocateMemory(size * INT);}// 獲取指定索引處的元素public int get(long i) {return unsafe.getInt(address + i * INT);}// 設置指定索引處的元素public void set(long i, int value) {unsafe.putInt(address + i * INT, value);}// 元素個數public long size() {return size;}// 釋放堆外內存public void freeMemory() {unsafe.freeMemory(address);} }
    • 測試
    @Testpublic void offHeap(){OffHeapArray offHeapArray = new OffHeapArray(4);offHeapArray.set(0, 1);offHeapArray.set(1, 2);offHeapArray.set(2, 3);offHeapArray.set(3, 4);offHeapArray.set(2, 5); // 在索引2的位置重復放入元素int sum = 0;for (int i = 0; i < offHeapArray.size(); i++) {sum += offHeapArray.get(i);}// 打印12System.out.println(sum);//一定要記得釋放內存回操作系統offHeapArray.freeMemory();}
  • CompareAndSwap操作
  • /*** @param o 包含要修改field的對象* @param offset 對象中某field的偏移量* @param expected 期望值* @param update 更新值* @return true | false*/ public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

    我們知道CAS是實現并發算法時常用到的一種技術。它的操作包含三個操作數——內存位置、預期原值及新值。執行CAS操作的時候,將內存位置的值與預期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值,否則,處理器不做任何操作。我們都知道,CAS是一條CPU的原子指令(cmpxchg指令),不會造成所謂的數據不一致問題,Unsafe提供的CAS方法(如compareAndSwapXXX)底層實現即為CPU指令cmpxchg。

    • 典型應用

    CAS在java.util.concurrent.atomic相關類、Java AQS、CurrentHashMap等實現上有非常廣泛的應用。如下圖所示,AtomicInteger的實現中:

    • 靜態字段valueOffset即為字段value的內存偏移地址;
    • valueOffset的值在AtomicInteger初始化時,在靜態代碼塊中通過Unsafe的objectFieldOffset方法獲取。
    • 在AtomicInteger中提供的線程安全方法中,通過字段valueOffset的值可以定位到AtomicInteger對象中value的內存地址,從而可以根據CAS實現對value字段的原子操作。


    下圖為某個AtomicInteger對象自增操作前后的內存示意圖,對象的基地址baseAddress=“0x110000”,通過baseAddress+valueOffset得到value的內存地址valueAddress=“0x11000c”;然后通過CAS進行原子性的更新操作,成功則返回,否則繼續重試,直到更新成功為止。

    • 基于Unsafe的compareAndSwapInt()方法構建線程安全的計數器
    • 計數類
    class Counter {//定義了一個volatile的字段count,以便對它的修改所有線程都可見,并在類加載的時候獲取count在類中的偏移地址private volatile int count = 0;private static long offset;private static Unsafe unsafe;static {try {Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);unsafe = (Unsafe) f.get(null);//獲取count偏移量offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count"));} catch (NoSuchFieldException | IllegalAccessException e) {e.printStackTrace();}}public void increment() {int before = count;// 失敗了就重試直到成功為止while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) {before = count;}}public int getCount() {return count;} }
    • 測試
    @Testpublic void count() throws InterruptedException {Counter counter = new Counter();ExecutorService threadPool = Executors.newFixedThreadPool(100);// 起100個線程,每個線程自增10000次IntStream.range(0, 100).forEach(i->threadPool.submit(()->IntStream.range(0, 10000).forEach(j->counter.increment())));threadPool.shutdown();Thread.sleep(2000);// 打印1000000System.out.println(counter.getCount());}
  • 線程調度
  • //取消阻塞線程 public native void unpark(Object thread); //阻塞線程 public native void park(boolean isAbsolute, long time); //獲得對象鎖(可重入鎖) @Deprecated public native void monitorEnter(Object o); //釋放對象鎖 @Deprecated public native void monitorExit(Object o); //嘗試獲取對象鎖 @Deprecated public native boolean tryMonitorEnter(Object o);

    方法park、unpark即可實現線程的掛起與恢復,將一個線程進行掛起是通過park方法實現的,調用park方法后,線程將一直阻塞直到超時或者中斷等條件出現;unpark可以終止一個掛起的線程,使其恢復正常。

    • 典型應用

    Java鎖和同步器框架的核心類AbstractQueuedSynchronizer,就是通過調用LockSupport.park()和LockSupport.unpark()實現線程的阻塞和喚醒的,而LockSupport的park、unpark方法實際是調用Unsafe的park、unpark方式來實現。

    參考文章
    參考文章

    總結

    以上是生活随笔為你收集整理的JAVA Unsafe类的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。