Unsafe?

<h4>1岖妄、了解Unsafe用法</h4>
翻看java.util.concurrent.atomic下面的源代碼。我們知道這些類主要是封裝了一些cas原子性操作:
其在java.util.concurrent、java.util.concurrent.locks兩個包中被大量使用铛碑。我們先找個AtomicInteger類來分析一下:
<pre>
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
<h6>private static final Unsafe unsafe = Unsafe.getUnsafe();</h6>
private static final long valueOffset;
static {
try {
//定位對象的字段在內存中偏移量
valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
public final int getAndSet(int newValue) {
for (;;) { //循環(huán)比較莉御,如果值沒有被修改過的話就直接更新牍颈,否則循環(huán)
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
<h6>
public final boolean compareAndSet(int expect, int update) { //判斷cas操作是否成功
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
</h6>
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int getAndIncrement() { //cas自增噪奄,返回原值
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final int getAndDecrement() {//cas自減
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return current;
}
}
public final int getAndAdd(int delta) { cas相加色罚,返回原值
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return current;
}
}
public final int incrementAndGet() { cas自增,返回新值
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final int decrementAndGet() { cas自減返回新值
for (;;) {
int current = get();
int next = current - 1;
if (compareAndSet(current, next))
return next;
}
}
public final int addAndGet(int delta) { cas相加,返回新值
for (;;) {
int current = get();
int next = current + delta;
if (compareAndSet(current, next))
return next;
}
}
public String toString() {
return Integer.toString(get());
}
public int intValue() {
return get();
}
public long longValue() {
return (long)get();
}
public float floatValue() {
return (float)get();
}
public double doubleValue() {
return (double)get();
}
}
</pre>
我們發(fā)現(xiàn)里面最核心的方法是:compareAndSwapInt禀晓。其實Unsafe只提供了3個核心的cas重付。
compareAndSwapInt、compareAndSwapLong、compareAndSwapObject 分別對應整數(shù)、長整數(shù)计福、對象的cas操作,整理了一下atomic類使用cas操作情況:

Paste_Image.png

當然Unsafe用處這么廣陶冷,其實它的問題也同樣明顯:
1、通過循環(huán)自旋的方式來做判斷胀莹,有一些時間消耗
2媳否、常說的ABA問題,即 因為CAS需要在操作值的時候檢查下值有沒有發(fā)生變化栈顷,如果沒有發(fā)生變化則更新逆日,但是如果一個值原來是A,變成了B萄凤,又變成了A室抽,那么使用CAS進行檢查時會發(fā)現(xiàn)它的值沒有發(fā)生變化,但是實際上卻變化過靡努。ABA問題的解決思路就是使用版本號坪圾。在變量前面追加上版本號,每次變量更新的時候把版本號加一惑朦,那么A-B-A 就會變成1A-2B-3A,在比較值的同時也比較版本號的值兽泄。不過也并不是所有的情況都需要這么做,如果我們只是對普通的整形計數(shù)的話就算有ABA的情況漾月,也不影響最終的賦值因為在最終的那個節(jié)點前我們認為值還是沒變的即可原子性的更新值病梢。關于ABA的問題詳細可參考:
http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
3、更多的是單個的值的原子性操作, 如需要實現(xiàn) a + b - c功能蜓陌,這個時候更多的是通過鎖的形式來同步實現(xiàn)
<h4>2觅彰、查看sun.misc.Unsafe的源碼</h4>
因為Unsafe類包裝了很多底層的、非安全的操作钮热。雖然該類及其所有的方法都是public的填抬,但是它只能被受信任的代碼使用,并發(fā)框架中的很多類隧期,以及Disruptor框架都是使用了Unsafe類飒责。下面我們就在sun.misc包下面找到Unsafe源碼看看:
下載jdk 1.7對應的openjdk源碼,說明鏈接:https://jdk7.java.net/source.html 下載鏈接:openjdk-7u40-fcs-src-b43-26_aug_2013.zip
解壓以后在openjdk\jdk\src\share\classes\sun\misc\Unsafe.java就能查看其源碼
Unsafe類(因為類太長我只列出部分比較重要)
<b>直接內存操作仆潮,如分配宏蛉、讀寫、釋放內存</b>
<pre>
public native long allocateMemory(long bytes);

public native long reallocateMemory(long address, long bytes);

public native void setMemory(Object o, long offset, long bytes, byte value);

public void setMemory(long address, long bytes, byte value);

public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);

public void copyMemory(long srcAddress, long destAddress, long bytes);

public native void freeMemory(long address);

public native int addressSize();

public native int pageSize();
</pre>
<b>定位對象的字段在內存中偏移量</b>
<pre>
public native long staticFieldOffset(Field f);

public native long objectFieldOffset(Field f);

public native Object staticFieldBase(Field f);

public native int arrayBaseOffset(Class arrayClass);

public static final int INVALID_FIELD_OFFSET = -1;

public static final int ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);

public static final int ARRAY_BYTE_BASE_OFFSE = theUnsafe.arrayBaseOffset(byte[].class);

public static final int ARRAY_SHORT_BASE_OFFSE = theUnsafe.arrayBaseOffset(short[].class);

public static final int ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);

public static final int ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);

public static final int ARRAY_LONG_BASE_OFFSET= theUnsafe.arrayBaseOffset(long[].class);

public static final int ARRAY_FLOAT_BASE_OFFSET= theUnsafe.arrayBaseOffset(float[].class);

public static final int ARRAY_DOUBLE_BASE_OFFSET= theUnsafe.arrayBaseOffset(double[].class);

public static final int ARRAY_OBJECT_BASE_OFFSET= theUnsafe.arrayBaseOffset(Object[].class);
</pre>
<b>cas操作:</b>
<pre>
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
</pre>
<b>Unsafe的getUnsafe的方法是有授權限制的:</b>
<pre>
@CallerSensitive
public static Unsafe getUnsafe() {
Class cc = Reflection.getCallerClass();
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
</pre>
正常情況下去應用Unsafe對象是會異常提示:

Paste_Image.png

<b>不過我們也是有辦法可以使我們的代碼授信:</b>
1性置、命令方式: java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
2檐晕、引用授信代碼:
<pre>
public class TestUnsafe {
public static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
final PrivilegedExceptionAction<Unsafe> action = new PrivilegedExceptionAction<Unsafe>() {
public Unsafe run() throws Exception {
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
return (Unsafe) theUnsafe.get(null);
}
};
unsafe = AccessController.doPrivileged(action);
}
catch (Exception e) {
throw new RuntimeException("Unable to load unsafe", e);
}
return unsafe;
}
public static void main(String[] args) {
getUnsafe();
}
}
</pre>
<h4>3、Unsafe c++實現(xiàn)</h4>
1蚌讼、在openjdk\hotspot\src\share\vm\prims下面找到unsafe.cpp
2、在openjdk\hotspot\src\os_cpu\windows_x86\vm下面找到atomic_windows_x86.inline.hpp
<b>Unsafe.CompareAndSwapInt:</b>
<pre>
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint
addr = (jint ) index_oop_from_field_offset_long(p, offset); //獲取原值的地址
return (jint)(Atomic::cmpxchg(x, addr, e)) == e; //判斷原值跟期望的值是否相等
UNSAFE_END
</pre>
<b>cmpxchg:</b>
<pre>
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint
dest, jint compare_value) {
// alternative for InterlockedCompareExchange
int mp = os::is_MP();
__asm {
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
LOCK_IF_MP(mp)
cmpxchg dword ptr [edx], ecx
}
}
</pre>
LOCK_IF_MP:(mp)
<pre>
// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
//程序會根據(jù)當前處理器的類型來決定是否為cmpxchg指令添加lock前綴个榕。如果程序是在多處理器上運行篡石,就為cmpxchg指令加上lock前綴(lock cmpxchg)。反之西采,如果程序是在單處理器上運行凰萨,就省略lock前綴(單處理器自身會維護單處理器內的順序一致性,不需要lock前綴提供的內存屏障效果)械馆。

define LOCK_IF_MP(mp) __asm cmp mp, 0 \

                   __asm je L0      \
                   __asm _emit 0xF0 \
                   __asm L0:

</pre>

所以Unsafe采用的是cpu級別的lock比synchronized的性能更加

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末胖眷,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霹崎,更是在濱河造成了極大的恐慌珊搀,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件尾菇,死亡現(xiàn)場離奇詭異境析,居然都是意外死亡,警方通過查閱死者的電腦和手機派诬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門劳淆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人默赂,你說我怎么就攤上這事沛鸵。” “怎么了缆八?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵曲掰,是天一觀的道長疾捍。 經(jīng)常有香客問我,道長蜈缤,這世上最難降的妖魔是什么拾氓? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮底哥,結果婚禮上咙鞍,老公的妹妹穿的比我還像新娘。我一直安慰自己趾徽,他們只是感情好续滋,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孵奶,像睡著了一般疲酌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上了袁,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天朗恳,我揣著相機與錄音,去河邊找鬼载绿。 笑死粥诫,一個胖子當著我的面吹牛,可吹牛的內容都是我干的崭庸。 我是一名探鬼主播怀浆,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼怕享!你這毒婦竟也來了执赡?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤函筋,失蹤者是張志新(化名)和其女友劉穎沙合,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體跌帐,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡灌诅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了含末。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片猜拾。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖佣盒,靈堂內的尸體忽然破棺而出挎袜,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布盯仪,位于F島的核電站紊搪,受9級特大地震影響,放射性物質發(fā)生泄漏全景。R本人自食惡果不足惜耀石,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爸黄。 院中可真熱鬧滞伟,春花似錦、人聲如沸炕贵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽称开。三九已至亩钟,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鳖轰,已是汗流浹背清酥。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蕴侣,地道東北人总处。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像睛蛛,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子胧谈,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內容

  • Unsafe類學習筆記 Unsafe 類初識 Unsafe位于sun.misc包內忆肾,看其命名就知道和注重安全性的j...
    Rinoux閱讀 4,000評論 1 6
  • Java8張圖 11、字符串不變性 12菱肖、equals()方法客冈、hashCode()方法的區(qū)別 13、...
    Miley_MOJIE閱讀 3,707評論 0 11
  • 翻譯自:unsafe Unsafe 實例 首先稳强,我們需要獲取到 Unsafe 對象的一個實例场仲。并沒有這樣一種 Un...
    石頭獅子閱讀 904評論 0 3
  • 實習生活已經(jīng)開始了大半年,各種悲催生活都已度過退疫,然而還是在迷茫的路上前行渠缕。 作為所謂在項目上實習的一...
    韻沫閱讀 139評論 0 0
  • 阿命:爸比的照片,爸比在學簡書怎么用
    知足江上人閱讀 122評論 0 0