Unsafe原理

java 生態(tài)圈物喷。 幾乎每個使用 java開發(fā)的工具、軟件基礎(chǔ)設(shè)施、高性能開發(fā)庫都在底層使用了 sun.misc.Unsafe 欺殿。這就是SUN未開源的sun.misc.Unsafe的類橡娄,該類功能很強大诗箍,涉及到類加載機制,其實例一般情況是獲取不到的挽唉,源碼中的設(shè)計是采用單例模式滤祖,不是系統(tǒng)加載初始化就會拋出SecurityException異常。Unsafe類官方并不對外開放瓶籽,因為Unsafe這個類提供了一些繞開JVM的更底層功能匠童,基于它的實現(xiàn)可以提高效率。

Unsafe API的大部分方法都是native實現(xiàn)

分為下面幾類:
Info:主要返回某些低級別的內(nèi)存信息:

public native int addressSize();
public native int pageSize();

Objects:主要提供Object和它的域操縱方法

public native Object allocateInstance(Class<?> var1) throws InstantiationException;
public native long objectFieldOffset(Field var1);

Class:主要提供Class和它的靜態(tài)域操縱方

public native long staticFieldOffset(Field var1);
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);
public native void ensureClassInitialized(Class<?> var1);

Arrays:數(shù)組操縱方法

public native int arrayBaseOffset(Class<?> var1);
public native int arrayIndexScale(Class<?> var1);

Synchronization:主要提供低級別同步原語

/** @deprecated */
@Deprecated
public native void monitorEnter(Object var1);
/** @deprecated */
@Deprecated
public native void monitorExit(Object var1);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public native void putOrderedInt(Object var1, long var2, int var4);

Memory:直接內(nèi)存訪問方法(繞過JVM堆直接操縱本地內(nèi)存)

public native long allocateMemory(long var1);
public native long reallocateMemory(long var1, long var3);
public native void setMemory(Object var1, long var2, long var4, byte var6);
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);

Unsafe類實例的獲取

Unsafe類設(shè)計只提供給JVM信任的啟動類加載器所使用塑顺,是一個典型的單例模式類

private Unsafe() {
}

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

可以通過反射技術(shù)暴力獲取Unsafe對象汤求,下面做一個cas算法的測試

package unsafe;

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class UnsafeCASTest {
    public static void main(String[] args) throws Exception {
        // 通過反射實例化Unsafe
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        // 實例化Player
        Player player = (Player) unsafe.allocateInstance(Player.class);
        player.setAge(18);
        player.setName("li lei");
        for (Field field : Player.class.getDeclaredFields()) {
            System.out.println(field.getName() + ":對應(yīng)的內(nèi)存偏移地址" + unsafe.objectFieldOffset(field));
        }

        System.out.println("-------------------");
        // unsafe.compareAndSwapInt(arg0, arg1, arg2, arg3)
        // arg0, arg1, arg2, arg3 分別是目標(biāo)對象實例,目標(biāo)對象屬性偏移量严拒,當(dāng)前預(yù)期值扬绪,要設(shè)的值

        int ageOffset = 12;
        // 修改內(nèi)存偏移地址為12的值(age),返回true,說明通過內(nèi)存偏移地址修改age的值成功
        System.out.println(unsafe.compareAndSwapInt(player, ageOffset, 18, 20));
        System.out.println("age修改后的值:" + player.getAge());
        System.out.println("-------------------");

        // 修改內(nèi)存偏移地址為12的值,但是修改后不保證立馬能被其他的線程看到裤唠。
        unsafe.putOrderedInt(player, 12, 33);
        System.out.println("age修改后的值:" + player.getAge());
        System.out.println("-------------------");

        // 修改內(nèi)存偏移地址為16的值挤牛,volatile修飾,修改能立馬對其他線程可見
        unsafe.putObjectVolatile(player, 16, "han mei");
        System.out.println("name修改后的值:" + unsafe.getObjectVolatile(player, 16));
    }
}

class Player {
    private int age;
    private String name;
    private Player() {
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }

}

輸出的結(jié)果是:

age:對應(yīng)的內(nèi)存偏移地址12
name:對應(yīng)的內(nèi)存偏移地址16
-------------------
true
age修改后的值:20
-------------------
age修改后的值:33
-------------------
name修改后的值:han mei

在concurrent包是基于AQS (AbstractQueuedSynchronizer)框架的巧骚,AQS框架借助于兩個類:

Unsafe(提供CAS操作)
LockSupport(提供park/unpark操作)

歸根結(jié)底赊颠,LockSupport.park()和LockSupport.unpark(Thread thread)調(diào)用的是Unsafe中的native代碼:

//LockSupport中
public static void park() {
        UNSAFE.park(false, 0L);
    }
//LockSupport中
public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }

Unsafe類中的對應(yīng)方法:

 //park
    public native void park(boolean isAbsolute, long time);
    
    //unpack
    public native void unpark(Object var1);

park函數(shù)是將當(dāng)前調(diào)用Thread阻塞格二,而unpark函數(shù)則是將指定線程Thread喚醒。

Unsafe.park和Unsafe.unpark的底層實現(xiàn)原理

在Linux系統(tǒng)下竣蹦,是用的Posix線程庫pthread中的mutex(互斥量)顶猜,condition(條件變量)來實現(xiàn)的。
mutex和condition保護了一個_counter的變量痘括,當(dāng)park時长窄,這個變量被設(shè)置為0,當(dāng)unpark時纲菌,這個變量被設(shè)置為1挠日。

每個Java線程都有一個Parker實例,Parker類是這樣定義的:

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}  

可以看到Parker類實際上用Posix的mutex翰舌,condition來實現(xiàn)的嚣潜。
在Parker類里的_counter字段,就是用來記錄“許可”的椅贱。

當(dāng)調(diào)用park時懂算,先嘗試能否直接拿到“許可”,即_counter>0時庇麦,如果成功计技,則把_counter設(shè)置為0,并返回:

void Parker::park(bool isAbsolute, jlong time) {  
  
  // Ideally we'd do something useful while spinning, such  
  // as calling unpackTime().  
  
  // Optional fast-path check:  
  // Return immediately if a permit is available.  
  // We depend on Atomic::xchg() having full barrier semantics  
  // since we are doing a lock-free update to _counter.  
  
  if (Atomic::xchg(0, &_counter) > 0) return;  

如果不成功山橄,則構(gòu)造一個ThreadBlockInVM垮媒,然后檢查_counter是不是>0,如果是航棱,則把_counter設(shè)置為0睡雇,unlock mutex并返回:

ThreadBlockInVM tbivm(jt);  
if (_counter > 0)  { // no wait needed  
  _counter = 0;  
  status = pthread_mutex_unlock(_mutex);  

否則,再判斷等待的時間丧诺,然后再調(diào)用pthread_cond_wait函數(shù)等待入桂,如果等待返回奄薇,則把_counter設(shè)置為0驳阎,unlock mutex并返回:

if (time == 0) {  
  status = pthread_cond_wait (_cond, _mutex) ;  
}  
_counter = 0 ;  
status = pthread_mutex_unlock(_mutex) ;  
assert_status(status == 0, status, "invariant") ;  
OrderAccess::fence();  

unpark
當(dāng)unpark時,則簡單多了馁蒂,直接設(shè)置_counter為1呵晚,再unlock mutex返回。如果_counter之前的值是0沫屡,則還要調(diào)用pthread_cond_signal喚醒在park中等待的線程:

void Parker::unpark() {  
  int s, status ;  
  status = pthread_mutex_lock(_mutex);  
  assert (status == 0, "invariant") ;  
  s = _counter;  
  _counter = 1;  
  if (s < 1) {  
     if (WorkAroundNPTLTimedWaitHang) {  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
     } else {  
        status = pthread_mutex_unlock(_mutex);  
        assert (status == 0, "invariant") ;  
        status = pthread_cond_signal (_cond) ;  
        assert (status == 0, "invariant") ;  
     }  
  } else {  
    pthread_mutex_unlock(_mutex);  
    assert (status == 0, "invariant") ;  
  }  
}  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饵隙,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子沮脖,更是在濱河造成了極大的恐慌金矛,老刑警劉巖芯急,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異驶俊,居然都是意外死亡娶耍,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門饼酿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榕酒,“玉大人,你說我怎么就攤上這事故俐∠胗ィ” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵药版,是天一觀的道長辑舷。 經(jīng)常有香客問我,道長槽片,這世上最難降的妖魔是什么惩妇? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮筐乳,結(jié)果婚禮上歌殃,老公的妹妹穿的比我還像新娘。我一直安慰自己蝙云,他們只是感情好氓皱,可當(dāng)我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勃刨,像睡著了一般波材。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上身隐,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天廷区,我揣著相機與錄音,去河邊找鬼贾铝。 笑死隙轻,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的垢揩。 我是一名探鬼主播玖绿,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叁巨!你這毒婦竟也來了斑匪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤锋勺,失蹤者是張志新(化名)和其女友劉穎蚀瘸,沒想到半個月后狡蝶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡贮勃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年牢酵,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衙猪。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡馍乙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出垫释,到底是詐尸還是另有隱情丝格,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布棵譬,位于F島的核電站显蝌,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏订咸。R本人自食惡果不足惜曼尊,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脏嚷。 院中可真熱鬧骆撇,春花似錦、人聲如沸父叙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趾唱。三九已至涌乳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甜癞,已是汗流浹背夕晓。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留悠咱,地道東北人蒸辆。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像乔煞,于是被迫代替她去往敵國和親吁朦。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,573評論 2 359

推薦閱讀更多精彩內(nèi)容