欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java?Unsafe如何花式操作內(nèi)存

 更新時(shí)間:2023年08月10日 10:41:54   作者:小新成長之路  
C++可以動(dòng)態(tài)的分類內(nèi)存,而java并不能這樣,是不是java就不能操作內(nèi)存呢,其實(shí)是有其他辦法可以操作內(nèi)存的,下面就一起看看Unsafe是如何花式操作內(nèi)存的吧

前言

C++可以動(dòng)態(tài)的分類內(nèi)存(但是得主動(dòng)釋放內(nèi)存,避免內(nèi)存泄漏),而java并不能這樣,java的內(nèi)存分配和垃圾回收統(tǒng)一由JVM管理,是不是java就不能操作內(nèi)存呢?當(dāng)然有其他辦法可以操作內(nèi)存,接下來有請(qǐng) Unsafe 出場,我們一起看看 Unsafe 是如何花式操作內(nèi)存的。

Unsafe介紹

Unsafe 見名知意,不安全的意思,因?yàn)橥ㄟ^這個(gè)類可以繞過JVM的管理直接去操作內(nèi)存,如果操作不當(dāng)或者使用完成后沒有及時(shí)釋放的話,這部分的內(nèi)存不會(huì)被回收,久而久之,這種沒有被釋放的內(nèi)存會(huì)越來越多造成內(nèi)存泄漏。所以這是一個(gè)比較不安全的操作,一般不建議直接使用,畢竟這種問題導(dǎo)致的線上問題很難查出,另外通常的解決辦法就是重啟系統(tǒng)了。雖然 Unsafe 是不安全的操作,但可以根據(jù)實(shí)際情況合理使用可以達(dá)到更好的效果,比如像java NIO里就有通過這種方式創(chuàng)建堆外存。 Unsafe 常用的操作包括:分配內(nèi)存釋放內(nèi)存、實(shí)例化及對(duì)象操作、數(shù)組操作、CAS、線程同步等。

Unsafe實(shí)例對(duì)象的獲取

Unsafe不能直接獲取到,像下面這樣使用會(huì)直接拋出安全檢查異常。

public static void main(String[] args) throws Exception {
    Unsafe unsafe = Unsafe.getUnsafe();
}

運(yùn)行結(jié)果就這樣:

Exception in thread "main" java.lang.SecurityException: Unsafe
    at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
    at com.star95.study.UnsafeTest.main(UnsafeTest.java:21)

我們來看看 sun.misc.Unsafe.getUnsafe 這個(gè)方法的源碼:

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

這里會(huì)判斷這個(gè)類的加載器是否是啟用類加載器 Bootstrap ClassLoader ,如果不是啟動(dòng)類加載器加載的則拋異常。 Bootstrap ClassLoader 這個(gè)類加載器主要加載java核心類庫,比如rt.jar這類包的,java采用的是雙親委托方式加載類,如果父加載器已加載了某個(gè)類,則子加載器不再加載,采用這樣的方式進(jìn)一步加強(qiáng)安全性。關(guān)于啟動(dòng)類加載器 Bootstrap ClassLoader 、擴(kuò)展類加載器 Extension Classloader 、應(yīng)用程序類加載器 Application Classloader 這里不再介紹,感興趣的可自行搜索。以下介紹3種方法來獲取 Unsafe 。

1、修改啟動(dòng)類加載器的搜索路徑

public class UnsafeUtil {
    private UnsafeUtil() {}
    public static Unsafe getUnsafe() {
        System.out.println("get getUnsafe...");
        return Unsafe.getUnsafe();
    }
}

就這一個(gè)類,打成一個(gè)jar包。

把這個(gè)jar包的路徑放到j(luò)vm啟動(dòng)參數(shù)里 -Xbootclasspath/a:D:\work\projects\test\unsafe\target\unsafe-1.0-SNAPSHOT.jar ,然后調(diào)用即可獲取到 Unsafe

關(guān)于jvm參數(shù) -Xbootclasspath 說明:

-Xbootclasspath:新jar路徑(Windows用;分隔,linux用:分隔),這種相當(dāng)于覆蓋了java默認(rèn)的核心類搜索路徑,包括核心類庫例如rt.jar,這種基本不用。

-Xbootclasspath/a:新jar路徑(Windows用;分隔,linux用:分隔),這種是把新jar路徑追加到已有的classpath后,相當(dāng)于擴(kuò)大了核心類的范圍,這個(gè)常用。

-Xbootclasspath/p:新jar路徑(Windows用;分隔,linux用:分隔),這種是把新jar路徑追加到已有的classpath前,也相當(dāng)于擴(kuò)大了核心類的范圍,但是放到核心類前可能會(huì)引起沖突,這個(gè)不常用。

2、利用反射使用構(gòu)造方法創(chuàng)建實(shí)例

Unsafe 有一個(gè)私有的無參構(gòu)造方法,利用反射使用構(gòu)造方法也可以創(chuàng)建 Unsafe 實(shí)例。

Constructor constructor = Unsafe.class.getDeclaredConstructors()[0];
constructor.setAccessible(true);
Unsafe unsafe = (Unsafe)constructor.newInstance();
System.out.println(unsafe);

3、利用反射獲取Unsafe屬性創(chuàng)建實(shí)例

Unsafe 類里有一個(gè)屬性 private static final Unsafe theUnsafe; 利用反射獲取到這個(gè)屬性然后對(duì) null 這個(gè)對(duì)象獲取屬性值就會(huì)觸發(fā)類靜態(tài)塊兒的執(zhí)行,從而達(dá)到實(shí)例化的目的。

private static Unsafe getUnsafe() {
    try {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        return (Unsafe)field.get(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

以上3種方法雖然都可以獲取到 Unsafe 的實(shí)例,但第三種更常用一些。

Unsafe常用操作

Unsafe類里大概有100多個(gè)方法,按用途主要分為以下幾大類,分別介紹。

Unsafe操作內(nèi)存

內(nèi)存操作主要包括內(nèi)存

//分配指定大小的內(nèi)存
public long allocateMemory(long bytes)
//根據(jù)給定的內(nèi)存地址address調(diào)整內(nèi)存大小
public long reallocateMemory(long address, long bytes)
//設(shè)置內(nèi)存值
public void setMemory(Object o, long offset, long bytes, byte value)
public void setMemory(long address, long bytes, byte value)
//內(nèi)存復(fù)制,支持兩種地址模式
public void copyMemory(Object srcBase, long srcOffset,  Object destBase, long destOffset, long bytes)
//釋放allocateMemory和reallocateMemory申請(qǐng)的內(nèi)存
public native void freeMemory(long address)

舉個(gè)栗子:

public void test() throws Exception {
        Unsafe unsafe = getUnsafe();
        long address = unsafe.allocateMemory(8);
        System.out.println("allocate memory with 8 bytes, address=" + address);
        long data = 13579L;
        unsafe.putLong(address, data);
        System.out.println("direct put data to address, data=" + data);
        System.out.println("get address data=" + unsafe.getLong(address));
        long address1 = unsafe.allocateMemory(8);
        System.out.println("allocate memory with 8 bytes, address1=" + address);
        unsafe.copyMemory(address, address1, 8);
        System.out.println("copy memory with 8 bytes to address1=" + address1);
        System.out.println("get address1 data=" + unsafe.getLong(address1));
        unsafe.reallocateMemory(address1, 16);
        unsafe.setMemory(address1, 16, (byte)20);
        System.out.println("after setMemory address1=" + unsafe.getByte(address1));
        unsafe.freeMemory(address1);
        unsafe.freeMemory(address);
        System.out.println("free memory over");
        long[] l1 = new long[] {11, 22, 33, 44};
        long[] l2 = new long[4];
        long offset = unsafe.arrayBaseOffset(long[].class);
        unsafe.copyMemory(l1, offset, l2, offset, 32);
        System.out.println("l2=" + Arrays.toString(l2));
    }

輸出結(jié)果:

allocate memory with 8 bytes, address=510790256
direct put data to address, data=13579
get address data=13579
allocate memory with 8 bytes, address1=510790256
copy memory with 8 bytes to address1=510788736
get address1 data=13579
after setMemory address1=20
free memory over
l2=[11, 22, 33, 44]

Unsafe操作類、對(duì)象及變量

下面介紹關(guān)于類、對(duì)象及變量相關(guān)的一些操作,還有一些其他的方法沒有一一列出,大家可以自行研究。

//實(shí)例化對(duì)象,不調(diào)構(gòu)造方法
Object allocateInstance(Class<?> cls)
//字段在內(nèi)存中的地址相對(duì)于實(shí)例對(duì)象內(nèi)存地址的偏移量
public long objectFieldOffset(Field f)
//字段在內(nèi)存中的地址相對(duì)于class內(nèi)存地址的偏移量
public long objectFieldOffset(Class<?> c, String name)
//靜態(tài)字段在class對(duì)象中的偏移
public long staticFieldOffset(Field f)
//獲得靜態(tài)字段所對(duì)應(yīng)類對(duì)象
public Object staticFieldBase(Field f)
//獲取對(duì)象中指定偏移量的int值,這里還有基本類型的其他其中,比如char,boolean,long等
public native int getInt(Object o, long offset);
//將int值放入指定對(duì)象指定偏移量的位置,這里還有基本類型的其他其中,比如char,boolean,long等
public native void putInt(Object o, long offset, int x);
//獲取obj對(duì)象指定offset的屬性對(duì)象
public native Object getObject(Object obj, long offset);
//將newObj對(duì)象放入指定obj對(duì)象指定offset偏移量的位置
public native void putObject(Object obj, long offset, Object newObj);

實(shí)戰(zhàn)一下吧

class Cat {
    private String name;
    private long speed;
    public Cat(String name, long speed) {
        this.name = name;
        this.speed = speed;
    }
    public Cat() {
        System.out.println("constructor...");
    }
    static {
        System.out.println("static...");
    }
    @Override
    public String toString() {
        return "Cat{" + "name='" + name + '\'' + ", speed=" + speed + '}';
    }
}
class Foo {
    private int age;
    private Cat cat;
    private static String defaultString = "default........";
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public Cat getCat() {
        return cat;
    }
    public void setCat(Cat cat) {
        this.cat = cat;
    }
}
//測試方法
public void test1() throws Exception {
    // 使用allocateInstance方法創(chuàng)建一個(gè)Cat實(shí)例,這里不會(huì)調(diào)用構(gòu)造方法,但是靜態(tài)塊會(huì)執(zhí)行,所以會(huì)輸出"static..."
    Cat cat = (Cat)unsafe.allocateInstance(Cat.class);
    System.out.println("allocateInstance cat--->" + cat);
    Foo f = new Foo();
    f.setAge(13);
    f.setCat(new Cat("ketty", 120));
    long ageOffset = unsafe.objectFieldOffset(Foo.class.getDeclaredField("age"));
    // 這個(gè)offset的屬性是一個(gè)Cat對(duì)象
    long catOffset = unsafe.objectFieldOffset(Foo.class.getDeclaredField("cat"));
    // 獲取靜態(tài)屬性的時(shí)候直接用Class對(duì)象,用實(shí)例對(duì)象的話會(huì)發(fā)生NPE異常
    long defaultStringOffset = unsafe.staticFieldOffset(Foo.class.getDeclaredField("defaultString"));
    System.out.println("get age=" + unsafe.getInt(f, ageOffset));
    System.out.println("get cat=" + unsafe.getObject(f, catOffset));
    System.out.println("get defaultString=" + unsafe.getObject(Foo.class, defaultStringOffset));
    System.out.println("---------------------");
    // 操作內(nèi)存放入新值
    unsafe.putInt(f, ageOffset, 100);
    unsafe.putObject(f, catOffset, new Cat("hello", 333));
    unsafe.putObject(f, defaultStringOffset, "new default string");
    System.out.println("after put then get age=" + unsafe.getInt(f, ageOffset));
    System.out.println("after put then get cat=" + unsafe.getObject(f, catOffset));
    System.out.println("after put then get defaultString=" + unsafe.getObject(f, defaultStringOffset));
    System.out.println("---------------------");
}

程序輸出如下:

static...
allocateInstance cat--->Cat{name='null', speed=0}
get age=13
get cat=Cat{name='ketty', speed=120}
get defaultString=default........
---------------------
after put then get age=100
after put then get cat=Cat{name='hello', speed=333}
after put then get defaultString=new default string
---------------------

Unsafe操作數(shù)組

數(shù)組除了8種基本類型外,還包括Object數(shù)組。在 Unsafe 類里是通過靜態(tài)塊來獲取這些數(shù)據(jù)。

public void test2() {
    // 獲取8種基本類型和Object類型數(shù)組的基礎(chǔ)偏移量,scale相關(guān)的可以理解每個(gè)類型對(duì)應(yīng)的值所占的大小
    // 通過輸出信息我們可以看到基礎(chǔ)偏移量都是16,scale除Object的是4外,基礎(chǔ)數(shù)據(jù)類型的scale就是相應(yīng)的字節(jié)大小
    System.out.println("Unsafe.ARRAY_BOOLEAN_BASE_OFFSET=" + Unsafe.ARRAY_BOOLEAN_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_BOOLEAN_INDEX_SCALE=" + Unsafe.ARRAY_BOOLEAN_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_BYTE_BASE_OFFSET=" + Unsafe.ARRAY_BYTE_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_BYTE_INDEX_SCALE=" + Unsafe.ARRAY_BYTE_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_SHORT_BASE_OFFSET=" + Unsafe.ARRAY_SHORT_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_SHORT_INDEX_SCALE=" + Unsafe.ARRAY_SHORT_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_CHAR_BASE_OFFSET=" + Unsafe.ARRAY_CHAR_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_CHAR_INDEX_SCALE=" + Unsafe.ARRAY_CHAR_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_INT_BASE_OFFSET=" + Unsafe.ARRAY_INT_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_INT_INDEX_SCALE=" + Unsafe.ARRAY_INT_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_LONG_BASE_OFFSET=" + Unsafe.ARRAY_LONG_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_LONG_INDEX_SCALE=" + Unsafe.ARRAY_LONG_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_FLOAT_BASE_OFFSET=" + Unsafe.ARRAY_FLOAT_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_FLOAT_INDEX_SCALE=" + Unsafe.ARRAY_FLOAT_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_DOUBLE_BASE_OFFSET=" + Unsafe.ARRAY_DOUBLE_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_DOUBLE_INDEX_SCALE=" + Unsafe.ARRAY_DOUBLE_INDEX_SCALE);
    System.out.println("Unsafe.ARRAY_OBJECT_BASE_OFFSET=" + Unsafe.ARRAY_OBJECT_BASE_OFFSET);
    System.out.println("Unsafe.ARRAY_OBJECT_INDEX_SCALE=" + Unsafe.ARRAY_OBJECT_INDEX_SCALE);
    System.out.println("------------------------------");
    // 基本數(shù)據(jù)數(shù)組類型操作
    int[] array = new int[] {11, 22, 33};
    /*
        改變最后一個(gè)元素的值,地址的算法就是:基礎(chǔ)地址+偏移量,這個(gè)偏移量就是類型占用的大小*位置,Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE
     */
    System.out.println("before put array[2]=" + array[2]);
    unsafe.putInt(array, (long)Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE,
        100);
    // 獲取最后一個(gè)元素的值
    System.out.println("after put array[2]=" + array[2]);
    // 也可以這么獲取,使用基礎(chǔ)地址+偏移量的方式
    System.out.println("after put array[2]=" + unsafe.getInt(array,
        (long)Unsafe.ARRAY_INT_BASE_OFFSET + (array.length - 1) * Unsafe.ARRAY_INT_INDEX_SCALE));
    System.out.println("-------------------");
    // Object類型數(shù)組操作
    Cat[] cats = {new Cat("cat1", 1), new Cat("cat2", 2), new Cat("cat3", 3)};
    System.out.println("before put cats[2]=" + cats[2]);
    unsafe.putObject(cats,
        (long)Unsafe.ARRAY_OBJECT_BASE_OFFSET + (cats.length - 1) * Unsafe.ARRAY_OBJECT_INDEX_SCALE,
        new Cat("newcat", 10000));
    // 獲取最后一個(gè)元素的值
    System.out.println("after put cats[2]=" + cats[2]);
    // 也可以這么獲取,使用基礎(chǔ)地址+偏移量的方式
    System.out.println("after put cats[2]=" + unsafe.getObject(cats,
        (long)Unsafe.ARRAY_OBJECT_BASE_OFFSET + (cats.length - 1) * Unsafe.ARRAY_OBJECT_INDEX_SCALE));
    System.out.println("-------------------");
}

輸出:

Unsafe.ARRAY_BOOLEAN_BASE_OFFSET=16
Unsafe.ARRAY_BOOLEAN_INDEX_SCALE=1
Unsafe.ARRAY_BYTE_BASE_OFFSET=16
Unsafe.ARRAY_BYTE_INDEX_SCALE=1
Unsafe.ARRAY_SHORT_BASE_OFFSET=16
Unsafe.ARRAY_SHORT_INDEX_SCALE=2
Unsafe.ARRAY_CHAR_BASE_OFFSET=16
Unsafe.ARRAY_CHAR_INDEX_SCALE=2
Unsafe.ARRAY_INT_BASE_OFFSET=16
Unsafe.ARRAY_INT_INDEX_SCALE=4
Unsafe.ARRAY_LONG_BASE_OFFSET=16
Unsafe.ARRAY_LONG_INDEX_SCALE=8
Unsafe.ARRAY_FLOAT_BASE_OFFSET=16
Unsafe.ARRAY_FLOAT_INDEX_SCALE=4
Unsafe.ARRAY_DOUBLE_BASE_OFFSET=16
Unsafe.ARRAY_DOUBLE_INDEX_SCALE=8
Unsafe.ARRAY_OBJECT_BASE_OFFSET=16
Unsafe.ARRAY_OBJECT_INDEX_SCALE=4
------------------------------
before put array[2]=33
after put array[2]=100
after put array[2]=100
-------------------
static...
before put cats[2]=Cat{name='cat3', speed=3}
after put cats[2]=Cat{name='newcat', speed=10000}
after put cats[2]=Cat{name='newcat', speed=10000}
-------------------

Tips:

如果操作的元素位置沒有在數(shù)組范圍內(nèi)的話,put和get操作不會(huì)異常,都會(huì)成功,因?yàn)檫@是內(nèi)存操作,使用的是基礎(chǔ)地址+偏移量,但是并沒有改變?cè)紨?shù)組的大小,put后可以獲取相應(yīng)位置的內(nèi)存數(shù)據(jù),在沒有put前調(diào)用get則獲取的是數(shù)據(jù)類型的默認(rèn)值。

CAS

比較并交換(Compare And Swap),在jvm里是一個(gè)原子操作,先獲取內(nèi)存的值,然后判斷內(nèi)存值和預(yù)期值是否相同,相同則更新為新值表示操作成功,不同則直接返回false,表明操作失敗。java里的JUC包下很多隊(duì)列或者鎖都采用了這種實(shí)現(xiàn)方式。

//每次都從主內(nèi)存獲取var1對(duì)象var2偏移量的long值
public native long getLongVolatile(Object var1, long var2);
//將var4值放入指定var1對(duì)象的var2偏移量位置,直接刷新到主內(nèi)存
public native void putLongVolatile(Object var1, long var2, long var4);
// 比較并替換原值為新值,操作成功返回true否則false,var1是指定對(duì)象,var2是偏移量地址,var4是預(yù)期的原值,var5是要更新的新值
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
// 自旋獲取原值并增加數(shù)值,var1是指定對(duì)象,var2是偏移量地址,var4是要增加的值
public final int getAndAddLong(Object var1, long var2, long var4) {
    int var5;
    do {
        var5 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var5, var5 + var4));
    return var5;
}
// 自旋獲取原值并設(shè)置新值,var1是指定對(duì)象,var2是偏移量地址,var4是要設(shè)置的新值
public final int getAndSetLong(Object var1, long var2, long var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var5, var4));
    return var5;
}
// 還有Object相關(guān)的cas操作這里沒有列出

舉幾個(gè)栗子

public void test3() throws Exception {
    Cat cat = new Cat("Kitty", 1000);
    long speedOffset = unsafe.objectFieldOffset(Cat.class.getDeclaredField("speed"));
    System.out.println("before putLongVolatile,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
    // 設(shè)置speed的值為2000
    unsafe.putLongVolatile(cat, speedOffset, 2000);
    System.out.println("after putLongVolatile,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
    // 到這里speed的值是2000,但是compareAndSwapLong里預(yù)期的值是3000,所以cas失敗,返回false
    System.out.println("compareAndSwapLong result:" + unsafe.compareAndSwapLong(cat, speedOffset, 3000, 4000));
    // 到這里speed的值是2000,但是compareAndSwapLong里預(yù)期的值是2000,cas更新成功,返回true
    System.out.println("compareAndSwapLong result:" + unsafe.compareAndSwapLong(cat, speedOffset, 2000, 4000));
    // cas后speed的值就是4000了
    System.out.println("after compareAndSwapLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
    // getAndAddLong會(huì)返回原值4000,新值=原值+10
    System.out.println("getAndAddLong:" + unsafe.getAndAddLong(cat, speedOffset, 10));
    // getAndAddLong后speed新值是4010
    System.out.println("after getAndAddLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
    // getAndSetLong會(huì)返回原值4010,新值=要設(shè)置的新值1000
    System.out.println("getAndSetLong:" + unsafe.getAndSetLong(cat, speedOffset, 1000));
    // getAndSetLong后speed新值是1000
    System.out.println("after getAndSetLong,getLongVolatile=" + unsafe.getLongVolatile(cat, speedOffset));
}

輸出結(jié)果:

static...
before putLongVolatile,getLongVolatile=1000
after putLongVolatile,getLongVolatile=2000
compareAndSwapLong result:false
compareAndSwapLong result:true
after compareAndSwapLong,getLongVolatile=4000
getAndAddLong:4000
after getAndAddLong,getLongVolatile=4010
getAndSetLong:4010
after getAndSetLong,getLongVolatile=1000

線程調(diào)度及同步

// 釋放線程讓其繼續(xù)執(zhí)行,多次調(diào)用只會(huì)生效一次,可以在park前調(diào)用
public native void unpark(Object thread);
// 阻塞線程,isAbsolute為true:表示絕對(duì)時(shí)間,time的單位是毫秒ms,false:表示相對(duì)時(shí)間,time的單位是納秒級(jí)的時(shí)間
public native void park(boolean isAbsolute, long time);
//以下3個(gè)方法均標(biāo)注過期了,建議使用其他同步方法
// 獲取var1的對(duì)象鎖,沒獲取到則阻塞等待
public native void monitorEnter(Object var1);
// 嘗試獲取var1的對(duì)象鎖,不阻塞,獲取到則返回true,沒獲取到返回false
public native boolean tryMonitorEnter(Object var1);
// 釋放var1對(duì)象鎖
public native void monitorExit(Object var1);

我們先看看monitor同步相關(guān)的測試:

public void test4() {
    Object obj = new Object();
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " isrunning...");
                unsafe.monitorEnter(obj);
                System.out.println(Thread.currentThread().getName() + " got monitorEnter...");
                Thread.sleep(3000);
                System.out.println(Thread.currentThread().getName() + " business over...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                unsafe.monitorExit(obj);
                System.out.println(Thread.currentThread().getName() + " monitorExit...");
            }
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " isrunning...");
                unsafe.monitorEnter(obj);
                System.out.println(Thread.currentThread().getName() + " got monitorEnter...");
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName() + " business over...");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                unsafe.monitorExit(obj);
                System.out.println(Thread.currentThread().getName() + " monitorExit...");
            }
        }
    });
    Thread t3 = new Thread(new Runnable() {
        @Override
        public void run() {
            boolean flag = false;
            try {
                System.out.println(Thread.currentThread().getName() + " isrunning...");
                flag = unsafe.tryMonitorEnter(obj);
                System.out.println(Thread.currentThread().getName() + " tryMonitorEnter:" + flag);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (flag) {
                    unsafe.monitorExit(obj);
                    System.out.println(Thread.currentThread().getName() + " monitorExit...");
                }
            }
            System.out.println(Thread.currentThread().getName() + " over...");
        }
    });
    t1.start();
    t2.start();
    t3.start();
}

可能的一種輸出如下(線程是根據(jù)系統(tǒng)分配資源調(diào)度的,輸出先后順序會(huì)有多種),下面的這個(gè)輸出我們可以看到先輸出3個(gè)線程都啟動(dòng)了,Thread-2嘗試獲取鎖失敗就結(jié)束了,然后Thread-0競爭到了對(duì)象鎖,等Thread-0線程運(yùn)行完畢釋放了鎖,Thread-1才會(huì)獲取到鎖繼續(xù)執(zhí)行直到結(jié)束釋放鎖。Tips:

monitor相關(guān)的方法已經(jīng)加了@deprecated注解,官方已經(jīng)不再建議使用,可以換成其他鎖或者同步方式

Thread-0 isrunning...
Thread-2 isrunning...
Thread-2 tryMonitorEnter:false
Thread-2 over...
Thread-1 isrunning...
Thread-0 got monitorEnter...
Thread-0 business over...
Thread-0 monitorExit...
Thread-1 got monitorEnter...
Thread-1 business over...
Thread-1 monitorExit...

我們?cè)趤砜纯磒ark、unpark相關(guān)的使用

public void test5() throws Exception {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    System.out
        .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " is running...");
    // 這里讓當(dāng)前線程阻塞6s,注意:如果park第一個(gè)參數(shù)是true的話,表示絕對(duì)時(shí)間,這個(gè)時(shí)間是毫秒級(jí)的,也就是系統(tǒng)時(shí)間,系統(tǒng)到這個(gè)絕對(duì)時(shí)間后才喚醒執(zhí)行
    unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(6));
    System.out
        .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " continue...");
    // 這里讓當(dāng)前線程阻塞3s,注意:如果park第一個(gè)參數(shù)是false的話,這個(gè)是納秒級(jí)別的時(shí)間,表示相對(duì)當(dāng)前時(shí)間3s后繼續(xù)喚醒執(zhí)行
    unsafe.park(false, TimeUnit.SECONDS.toNanos(3));
    System.out
        .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " continue...");
    // 如果park的第一個(gè)參數(shù)是false,第二個(gè)值是0,則會(huì)一直等待,直到其他線程調(diào)用了unpark這個(gè)線程才會(huì)結(jié)束阻塞
    // 一般像這種無限期等待的調(diào)了多少次park(false, 0)就要對(duì)于調(diào)同樣次數(shù)的unpark才會(huì)完全解除阻塞
    unsafe.park(false, 0);
}

輸出:

2020-05-11 08:01:04 main is running...
2020-05-11 08:01:10 main continue...
2020-05-11 08:01:13 main continue...

再看一個(gè)案例:

public void test6() throws Exception {
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
                    + " is running...");
                // 這里讓當(dāng)前線程阻塞600s也就是10分鐘,注意:如果park第一個(gè)參數(shù)是true的話,表示絕對(duì)時(shí)間,這個(gè)時(shí)間是毫秒級(jí)的,也就是系統(tǒng)時(shí)間,系統(tǒng)到這個(gè)絕對(duì)時(shí)間后才喚醒執(zhí)行
                unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
                System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
                    + " continue...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
                    + " is running...");
                // 這里讓當(dāng)前線程阻塞600s也就是10分鐘,注意:如果park第一個(gè)參數(shù)是true的話,表示絕對(duì)時(shí)間,這個(gè)時(shí)間是毫秒級(jí)的,也就是系統(tǒng)時(shí)間,系統(tǒng)到這個(gè)絕對(duì)時(shí)間后才喚醒執(zhí)行
                unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
                // unsafe.park(true, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(600));
                System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName()
                    + " continue...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
    t1.start();
    t2.start();
    // 主線程休眠2秒
    Thread.sleep(2000);
    System.out
        .println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " is running...");
    // 這里調(diào)了unpark方法,參數(shù)就是t1線程,unsafe.unpark喚醒了t1線程,使得t1線程不用等到10分鐘立馬就可以執(zhí)行
    unsafe.unpark(t1);
    // 下面連續(xù)調(diào)用了兩次unpark t2線程,但是結(jié)果只釋放了一次令牌,如果把t2線程的unsafe.park注釋去掉,那么t2線程會(huì)一直等到park的時(shí)間到后被喚醒執(zhí)行,
    unsafe.unpark(t2);
    unsafe.unpark(t2);
    System.out.println(LocalDateTime.now().format(formatter) + " " + Thread.currentThread().getName() + " over...");
}

輸出如下:

2020-05-11 08:05:26 Thread-1 is running...
2020-05-11 08:05:26 Thread-0 is running...
2020-05-11 08:05:28 main is running...
2020-05-11 08:05:28 main over...
2020-05-11 08:05:28 Thread-1 continue...
2020-05-11 08:05:28 Thread-0 continue...

unsafe的park和unpark在JUC并發(fā)包下使用的特別多,后續(xù)再介紹吧

內(nèi)存屏障

這個(gè)我沒有深入的了解,網(wǎng)上找了些資料看了看,沒有具體的實(shí)踐過,大家可以了解下。

  • loadFence:保證在這個(gè)屏障之前的所有讀操作都已經(jīng)完成。
  • storeFence:保證在這個(gè)屏障之前的所有寫操作都已經(jīng)完成。
  • fullFence:保證在這個(gè)屏障之前的所有讀寫操作都已經(jīng)完成。

其他

類加載,類實(shí)例化相關(guān)的一些方法,還有其他的方法,這里不再一一說明,雖然不常用,但是了解其運(yùn)行原理,或者去研究一下jvm的源碼對(duì)自己都是一種提升。

以上就是詳解Java Unsafe如何花式操作內(nèi)存的詳細(xì)內(nèi)容,更多關(guān)于Java Unsafe的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java中四種線程池的使用示例詳解

    Java中四種線程池的使用示例詳解

    這篇文章主要給大家介紹了關(guān)于Java中四種線程池的使用方法,四種線程池分別包括FixedThreadPool、CachedThreadPool、ScheduledThreadPool以及SingleThreadExecutor,文中給出了詳細(xì)的示例代碼供大家參考,需要的朋友們下面來一起看看吧。
    2017-08-08
  • mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache)

    mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache)

    這篇文章主要介紹了mybatis教程之查詢緩存(一級(jí)緩存二級(jí)緩存和整合ehcache),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-05-05
  • Java中的異常Exception與處理方式詳解

    Java中的異常Exception與處理方式詳解

    這篇文章主要介紹了Java中的異常Exception與處理方式詳解, Java語言中,將程序執(zhí)行中發(fā)生的不正常情況稱為"異常"(開發(fā)過程中的語法錯(cuò)誤和邏輯錯(cuò)誤不是異常),需要的朋友可以參考下
    2024-01-01
  • @Transactional和@DS怎樣在事務(wù)中切換數(shù)據(jù)源

    @Transactional和@DS怎樣在事務(wù)中切換數(shù)據(jù)源

    這篇文章主要介紹了@Transactional和@DS怎樣在事務(wù)中切換數(shù)據(jù)源問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • javaMybatis映射屬性,高級(jí)映射詳解

    javaMybatis映射屬性,高級(jí)映射詳解

    下面小編就為大家?guī)硪黄猨avaMybatis映射屬性,高級(jí)映射詳解。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-11-11
  • Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析

    Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析

    這篇文章主要介紹了Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析,多態(tài)是面向?qū)ο缶幊陶Z言的重要特性,它允許基類的指針或引用指向派生類的對(duì)象,而在具體訪問時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,需要的朋友可以參考下
    2024-01-01
  • Java實(shí)現(xiàn)JSP在Servelt中連接Oracle數(shù)據(jù)庫的方法

    Java實(shí)現(xiàn)JSP在Servelt中連接Oracle數(shù)據(jù)庫的方法

    這篇文章主要介紹了Java實(shí)現(xiàn)JSP在Servelt中連接Oracle數(shù)據(jù)庫的方法,需要的朋友可以參考下
    2014-07-07
  • SpringBoot+mybatis+Vue實(shí)現(xiàn)前后端分離項(xiàng)目的示例

    SpringBoot+mybatis+Vue實(shí)現(xiàn)前后端分離項(xiàng)目的示例

    本文主要介紹了SpringBoot+mybatis+Vue實(shí)現(xiàn)前后端分離項(xiàng)目的示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-12-12
  • java new一個(gè)對(duì)象的過程實(shí)例解析

    java new一個(gè)對(duì)象的過程實(shí)例解析

    這篇文章主要介紹了java new一個(gè)對(duì)象的過程實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-12-12
  • Java多線程Semaphore工具的使用詳解

    Java多線程Semaphore工具的使用詳解

    Semaphore 是一種用于控制線程并發(fā)訪問數(shù)的同步工具。它通過維護(hù)一定數(shù)量的許可證來限制對(duì)共享資源的訪問,許可證的數(shù)量就是可以同時(shí)訪問共享資源的線程數(shù)目,需要的朋友可以參考下
    2023-05-05

最新評(píng)論