亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

神奇的Unsafe,你get了嗎

ideaa / 1048人閱讀

摘要:它的目的是為了增強(qiáng)語言直接操作底層資源的能力,無疑帶來很多方便。這樣可以有效避免多線程環(huán)境下的同步問題。另外還有的匿名內(nèi)部類的生成,數(shù)組內(nèi)存操作等。

本文原創(chuàng)地址,我的博客:jsbintask的博客(食用效果最佳),轉(zhuǎn)載請(qǐng)注明出處!

簡(jiǎn)介

Unsafe是jdk提供的一個(gè)直接訪問操作系統(tǒng)資源的工具類(底層c++實(shí)現(xiàn)),它可以直接分配內(nèi)存,內(nèi)存復(fù)制,copy,提供cpu級(jí)別的CAS樂觀鎖等操作。它的目的是為了增強(qiáng)java語言直接操作底層資源的能力,無疑帶來很多方便。但是,使用的同時(shí)就得額外小心!它的總體作用如下(圖片來源網(wǎng)絡(luò)):

Unsafe位于sun.misc包下,jdk中的并發(fā)編程包juc(java.util.concurrent)基本全部靠Unsafe實(shí)現(xiàn),由此可見其重要性。

基本使用

Unsafe被設(shè)計(jì)為單例,并且只允許被引導(dǎo)類加載器(BootstrapClassLoader)加載的類使用:

所以我們自己寫的類是無法直接通過Unsafe.getUnsafe()獲取的。當(dāng)然,既然是java代碼,我們就可以使用一點(diǎn)歪道,比如通過反射直接new一個(gè)或者將其內(nèi)部靜態(tài)成員變量theUnsafe獲取出來:

public static void main(String[] args) throws Exception{
    // method 1
    Class unsafeClass = Unsafe.class;
    Constructor constructor = unsafeClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    Unsafe unsafe1 = constructor.newInstance();
    System.out.println(unsafe1);

    // method2
    Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Unsafe unsafe2 = (Unsafe) theUnsafe.get(null);
    System.out.println(unsafe2);
}

現(xiàn)在我們能夠在自己代碼里面使用Unsafe了,接下來看下它的使用以及jdk使用操作的。

CAS

CAS譯為Compare And Swap,它是樂觀鎖的一種實(shí)現(xiàn)。假設(shè)內(nèi)存值為v,預(yù)期值為e,想要更新成得值為u,當(dāng)且僅當(dāng)內(nèi)存值v等于預(yù)期值e時(shí),才將v更新為u。 這樣可以有效避免多線程環(huán)境下的同步問題。

在unsafe中,實(shí)現(xiàn)CAS算法通過cpu的原子指令cmpxchg實(shí)現(xiàn),它對(duì)應(yīng)的方法如下:

簡(jiǎn)單介紹下它使用的參數(shù),var1為內(nèi)存中要操作的對(duì)象,var2為要操作的值的內(nèi)存地址偏移量,var4為預(yù)期值,var5為想要更新成的值。

為了方便理解,舉個(gè)栗子。類User有一個(gè)成員變量name。我們new了一個(gè)對(duì)象User后,就知道了它在內(nèi)存中的起始值,而成員變量name在對(duì)象中的位置偏移是固定的。這樣通過這個(gè)起始值和這個(gè)偏移量就能夠定位到name在內(nèi)存中的具體位置。

所以我們現(xiàn)在的問題就是如何得出name在對(duì)象User中的偏移量,Unsafe自然也提供了相應(yīng)的方法:

他們分別為獲取靜態(tài)成員變量,成員變量的方法,所以我們可以使用unsafe直接更新內(nèi)存中的值:

public class UnsafeTest {
    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);

        User user = new User("jsbintask");
        long nameOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));
        unsafe.compareAndSwapObject(user, nameOffset, "jsbintask1", "jsbintask2");
        System.out.println("第一次更新后的值:" + user.getName());
        unsafe.compareAndSwapObject(user, nameOffset, "jsbintask", "jsbintask2");
        System.out.println("第二次更新后的值:" + user.getName());
    }
}

class User {
    private String name;

    public User(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

因?yàn)閮?nèi)存中name的值為"jsbintask",而第一次使用compareAndSwapObject方法預(yù)期值為"jsbintask1",這顯然是不相等的,所以第一次更新失敗,第二次我們傳入了正確的預(yù)期值,更新成功!

如果我們分析juc包下的Atomic開頭的原子類就會(huì)發(fā)現(xiàn),它內(nèi)部的原子操作全部來源于unsafe的CAS方法,比如AtomicInteger的getAndIncrement方法,內(nèi)部直接調(diào)用unsafe的getAndAddInt方法,它的實(shí)現(xiàn)原理為:cas失敗,就循環(huán),直到成功為止,這就是我們所說的自旋鎖!

內(nèi)存分配

Unsafe還給我們提供了直接分配內(nèi)存,釋放內(nèi)存,拷貝內(nèi)存,內(nèi)存設(shè)置等方法,值得注意的是,這里的內(nèi)存指的是堆外內(nèi)存!它是不受jvm內(nèi)存模型掌控的,所以使用需要及其小心:

//分配內(nèi)存, 相當(dāng)于C++的malloc函數(shù)
public native long allocateMemory(long bytes);
//釋放內(nèi)存
public native void freeMemory(long address);
//在給定的內(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);
//為給定地址設(shè)置值,忽略修飾限定符的訪問限制,與此類似操作還有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);

我們可以寫一段代碼驗(yàn)證一下:

public static void main(String[] args) throws Exception {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);

    // 分配 10M的堆外內(nèi)存
    long _10M_Address = unsafe.allocateMemory(1 * 1024 * 1024 * 10);
    // 將10M內(nèi)存的 前面1M內(nèi)存值設(shè)置為10
    unsafe.setMemory(_10M_Address, 1 * 1024 * 1024 * 1, (byte) 10);
    // 獲取第1M內(nèi)存的值: 10
    System.out.println(unsafe.getByte(_10M_Address + 1000));
    // 獲取第1M內(nèi)存后的值: 0(沒有設(shè)置)
    System.out.println(unsafe.getByte(_10M_Address + 1 * 1024 * 1024 * 5));
}

我們分配了10M內(nèi)存,并且將前1M內(nèi)存的值設(shè)置為了10,取出了內(nèi)存中的值進(jìn)行比較,驗(yàn)證了unsafe的方法。

堆外內(nèi)存不受jvm內(nèi)存模型掌控,在nio(netty,mina)中大量使用對(duì)外內(nèi)存進(jìn)行管道傳輸,copy等,使用它們的好處如下:

對(duì)垃圾回收停頓的改善。由于堆外內(nèi)存是直接受操作系統(tǒng)管理而不是JVM,所以當(dāng)我們使用堆外內(nèi)存時(shí),即可保持較小的堆內(nèi)內(nèi)存規(guī)模。從而在GC時(shí)減少回收停頓對(duì)于應(yīng)用的影響。

提升程序I/O操作的性能。通常在I/O通信過程中,會(huì)存在堆內(nèi)內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝操作,對(duì)于需要頻繁進(jìn)行內(nèi)存間數(shù)據(jù)拷貝且生命周期較短的暫存數(shù)據(jù),都建議存儲(chǔ)到堆外內(nèi)存。 而在jdk中,堆外內(nèi)存對(duì)應(yīng)的類為DirectByteBuffer,它內(nèi)部也是通過unsafe分配的內(nèi)存:

這里值得注意的是,對(duì)外內(nèi)存的回收借助了Cleaner這個(gè)類。

線程調(diào)度

通過Unsafe還可以直接將某個(gè)線程掛起,這和調(diào)用Object.wait()方法作用是一樣的,但是效率確更高!

我們熟知的AQS(AbstractQueuedSynchronizer)內(nèi)部掛起線程使用了LockSupport方法,而LockSupport內(nèi)部依舊使用的是Unsafe:
我們同樣可以寫一段代碼驗(yàn)證:

public static void main(String[] args) throws Exception {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
    theUnsafe.setAccessible(true);
    Unsafe unsafe = (Unsafe) theUnsafe.get(null);

    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                // i == 5時(shí),將當(dāng)前線程掛起
                unsafe.park(false, 0L);
            }
            System.out.println(Thread.currentThread().getName() + " printing i : " + i);
        }
    }, " Thread__Unsafe__1");

    t1.start();

    // 主線程休息三秒
    Thread.sleep(3000L);
    for (int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName() + " printing i : " + i);
        if (i == 9) {
            // 將線程 t1 喚醒
            unsafe.unpark(t1);
        }
    }

    System.in.read();
}

當(dāng)線程t1運(yùn)行到i=5時(shí),被掛起,主線程執(zhí)行,而主線程運(yùn)行到i=9時(shí),將t1喚醒,t1繼續(xù)打?。?在park出debug可以觀察t1線程的狀態(tài):

數(shù)組操作

對(duì)于數(shù)組,Unsafe提供了特別的方法返回不同類型數(shù)組在內(nèi)存中的偏移量:

arrayBaseOffset方法返回?cái)?shù)組在內(nèi)存中的偏移量,這個(gè)值是固定的。arrayIndexScale返回?cái)?shù)組中的每一個(gè)元素的內(nèi)存地址換算因子。舉個(gè)栗子,double數(shù)組(注意不是包裝類型)每個(gè)元素占用8個(gè)字節(jié),所以換算因子為8,int類型則為4,通過這兩個(gè)方法我們就能定位數(shù)組中每個(gè)元素的內(nèi)存地址,從而賦值,下面代碼演示:

public static void main(String[] args) throws Exception{
    Class unsafeClass = Unsafe.class;
    Constructor constructor = unsafeClass.getDeclaredConstructor();
    constructor.setAccessible(true);
    Unsafe unsafe = constructor.newInstance();

    Integer[] integers = new Integer[10];
    // 打印數(shù)組的原始值
    System.out.println(Arrays.toString(integers));
    // 獲取Integer數(shù)組在內(nèi)存中的固定的偏移量
    long arrayBaseOffset = unsafe.arrayBaseOffset(Integer[].class);
    System.out.println(unsafe.arrayIndexScale(Integer[].class));
    System.out.println(unsafe.arrayIndexScale(double[].class));
    // 將數(shù)組中第一個(gè)元素的更新為100
    unsafe.putObject(integers, arrayBaseOffset, 100);
    // 將數(shù)組中第五個(gè)元素更新為50  注意 引用類型占用4個(gè)字節(jié),所以內(nèi)存地址 需要 4 * 4 = 16
    unsafe.putObject(integers, arrayBaseOffset + 16, 50);
    // 打印更新后的值
    System.out.println(Arrays.toString(integers));
}

我們通過獲取Integer數(shù)組的內(nèi)存偏移量,結(jié)合換算因子將第一個(gè)元素,第五個(gè)元素分別替換為了100,50。驗(yàn)證了我們的說法。

數(shù)組的原子操作,juc包也已經(jīng)提供了相應(yīng)的工具類,比如AtomicIntegerArray內(nèi)部就是同過Unsafe的上述方法實(shí)現(xiàn)了數(shù)組的原子操作。

其它操作

Unsafe還提供了操作系統(tǒng)級(jí)別的方法如獲取內(nèi)存頁的大小public native int pageSize();,獲取系統(tǒng)指針大小public native int addressSize(); jdk8還加入了新的方法,內(nèi)存屏障,它的目的是為了防止指令重排序(編譯器為了優(yōu)化速度,會(huì)在保證單線程不出錯(cuò)的情況下將某些代碼的順序調(diào)換,比如先分配內(nèi)存,或者先返回引用等,這樣在多線程環(huán)境下就會(huì)出錯(cuò)):

//內(nèi)存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//內(nèi)存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//內(nèi)存屏障,禁止load、store操作重排序
public native void fullFence();

jdk1.8引入的StampedLock就是基于此實(shí)現(xiàn)的樂觀讀寫鎖. 另外,jdk1.8引入了lambda表達(dá)式,它其實(shí)會(huì)幫我們調(diào)用Unsafe的public native Class<");方法生成匿名內(nèi)部類,如下面的代碼:

public class UnsafeTest2 {
    public static void main(String[] args) {
        Function function = Integer::parseInt;
        System.out.println(function.apply("100"));
    }
}

查看字節(jié)碼:

發(fā)現(xiàn)它調(diào)用了LambdaMetafactory.metafactory方法,最終調(diào)用了InnerClassLambdaMetafactory的spinInnerClass方法:

總結(jié)

通過反射可以獲取Unsafe類的實(shí)例,他可以幫助我們進(jìn)行堆外內(nèi)存操作,內(nèi)存copy,內(nèi)存復(fù)制,線程掛起,提供了cpu級(jí)別的cas原子操作。另外還有l(wèi)ambda的匿名內(nèi)部類的生成,數(shù)組內(nèi)存操作等。juc包基本全部基于此類實(shí)現(xiàn)!

關(guān)注我,這里只有干貨!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/7198.html

相關(guān)文章

  • Java Executors 源碼分析

    摘要:表示一個(gè)異步任務(wù)的結(jié)果,就是向線程池提交一個(gè)任務(wù)后,它會(huì)返回對(duì)應(yīng)的對(duì)象。它們分別提供兩個(gè)重要的功能阻塞當(dāng)前線程等待一段時(shí)間直到完成或者異常終止取消任務(wù)。此時(shí),線程從中返回,然后檢查當(dāng)前的狀態(tài)已經(jīng)被改變,隨后退出循環(huán)。 0 引言 前段時(shí)間需要把一個(gè)C++的項(xiàng)目port到Java中,因此時(shí)隔三年后重新熟悉了下Java。由于需要一個(gè)通用的線程池,自然而然就想到了Executors。 用了...

    itvincent 評(píng)論0 收藏0
  • 深入理解 Go panic and recover

    摘要:恢復(fù)流程如下判斷當(dāng)前中的是否已標(biāo)注為處理從鏈表中刪除已標(biāo)注中止的事件,也就是刪除已經(jīng)被恢復(fù)的事件將相關(guān)需要恢復(fù)的棧幀信息傳遞給方法的參數(shù)每個(gè)棧幀對(duì)應(yīng)著一個(gè)未運(yùn)行完的函數(shù)。 作為一個(gè) gophper,我相信你對(duì)于 panic 和 recover 肯定不陌生,但是你有沒有想過。當(dāng)我們執(zhí)行了這兩條語句之后。底層到底發(fā)生了什么事呢?前幾天和同事剛好聊到相關(guān)的話題,發(fā)現(xiàn)其實(shí)大家對(duì)這塊理解還是比較...

    banana_pi 評(píng)論0 收藏0
  • 有點(diǎn)不安全卻又一亮 Go unsafe.Pointer

    摘要:因?yàn)樗遣话踩?,但是在特殊的?chǎng)景下,使用了它??梢源蚱频念愋秃蛢?nèi)存安全機(jī)制,讓你獲得眼前一亮的驚喜效果 在上一篇文章 《深入理解 Go Slice》 中,大家會(huì)發(fā)現(xiàn)其底層數(shù)據(jù)結(jié)構(gòu)使用了 unsafe.Pointer。因此想著再介紹一下其關(guān)聯(lián)知識(shí) 原文地址:有點(diǎn)不安全卻又一亮的 Go unsafe.Pointer 前言 在大家學(xué)習(xí) Go 的時(shí)候,肯定都學(xué)過 Go 的指針是不支持指針運(yùn)算...

    kviccn 評(píng)論0 收藏0
  • JVM筆記-13

    摘要:不可變?nèi)缃^對(duì)線程安全就是滿足并發(fā)編程實(shí)戰(zhàn)中對(duì)線程安全的定義在中標(biāo)注自己是線程安全的類,大都不是絕對(duì)的線程安全。如的和二實(shí)現(xiàn)線程安全的方法互斥同步互斥是因同步是果互斥是方法同步是目的。 一.到底什么叫線程安全:java并發(fā)編程實(shí)戰(zhàn)中對(duì)線程安全的定義是:當(dāng)多個(gè)線程訪問一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,...

    cyixlq 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<