正文前先來一波福利推薦:
福利一:
百萬年薪架構師視頻,該視頻可以學到很多東西,是本人花錢買的VIP課程,學習消化了一年,為了支持一下女朋友公眾號也方便大家學習,共享給大家。
福利二:
畢業答辯以及工作上各種答辯,平時積累了不少精品PPT,現在共享給大家,大大小小加起來有幾千套,總有適合你的一款,很多是網上是下載不到。
獲取方式:
微信關注 精品3分鍾 ,id為 jingpin3mins,關注后回復 百萬年薪架構師 ,精品收藏PPT 獲取雲盤鏈接,謝謝大家支持!
-----------------------正文開始---------------------------
Unsafe類的介紹
Java中基於操作系統級別的原子操作類sun.misc.Unsafe,它是Java中對大多數鎖機制實現的最基礎類。請注意,JDK 1.8和之前JDK版本的中sun.misc.Unsafe類可提供的方法有較大的變化,本文基於JDK 1.8。sun.misc.Unsafe類提供的原子操作基於操作系統直接對CPU進行操作,而以下這些方法又是sun.misc.Unsafe類中經常被使用的:
java不能直接訪問操作系統底層,而是通過本地方法來訪問。Unsafe類提供了硬件級別的原子操作,主要提供了以下功能:
1、獲取Unsafe對象實例
Unsafe類是一個單例,調用的方法為 getUnsafe(),在獲取實例的函數中有一步判斷,判斷檢查該CallerClass是不是由系統類加載器BootstrapClassLoader
加載,我們知道bootstrapClass是最高層的加載器,底層由C++實現,沒有父類加載器,所有由該系統類加載器加載的類調用getClassLoader()會返回null,所以要檢查類是否為bootstrap加載器加載只需要檢查該方法是不是返回null。
@CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
上邊獲得 theUnsafe 對象是java內部使用的,因為 JDK源碼中對這個類進行了嚴格限制,我們不能通過常規new的方式去獲取該類的實例,也不能通過Unsafe.getUnsafe 獲得Unsafe對象實例;
那么我們通過什么方式獲得該對象實例,這里就用到 強大的反射機制 自帶暴力訪問buff :
在Unsafe類中有一個私有成員變量theUnsafe
,因此我們可以通過反射將private單例實例的accessible設置為true,然后通過Field的get方法獲取,如下。
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //獲得名為 theUnsafe 的屬性 即使為私有 同樣獲得 f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); //如果為靜態屬性 則get 參數為null
2、通過Unsafe類可以分配內存,可以釋放內存;
類中提供的3個本地方法allocateMemory、reallocateMemory、freeMemory分別用於分配內存,擴充內存和釋放內存,與C語言中的3個方法對應: malloc(), realloc(), free() [直接開辟內存,釋放內存,操作處理使用指針];這里不過多介紹;
public native long allocateMemory(long l); public native long reallocateMemory(long l, long l1); public native void freeMemory(long l);
3、可以定位對象某字段的內存位置,也可以修改對象的字段值,即使它是私有的;
字段的定位:
對象的字段在主存中的偏移位置:ObjectFieldOffSet staticFieldOffset
JAVA中對象的字段的定位可能通過staticFieldOffset方法實現,該方法返回給定field的內存地址偏移量,這個值對於給定的filed是唯一的且是固定不變的。
對象的整型或者雙精度浮點型在主存中的偏移位置:getIntVolatile, getLong
getIntVolatile方法獲取對象中offset偏移地址對應的整型field的值,支持volatile load語義。
getLong方法獲取對象中offset偏移地址對應的long型field的值
對象的數組元素定位:arrayBaseOffset, arrayIndexScale
Unsafe類中有很多以BASE_OFFSET結尾的常量,比如ARRAY_INT_BASE_OFFSET,ARRAY_BYTE_BASE_OFFSET等,這些常量值是通過arrayBaseOffset方法得到的。arrayBaseOffset方法是一個本地方法,可以獲取數組第一個元素的偏移地址。Unsafe類中還有很多以INDEX_SCALE結尾的常量,比如 ARRAY_INT_INDEX_SCALE , ARRAY_BYTE_INDEX_SCALE等,這些常量值是通過arrayIndexScale方法得到的。arrayIndexScale方法也是一個本地方法,可以獲取數組的轉換因子,也就是數組中元素的增量地址。將arrayBaseOffset與arrayIndexScale配合使用,可以定位數組中每個元素在內存中的位置。
public final class Unsafe { public static final int ARRAY_INT_BASE_OFFSET; public static final int ARRAY_INT_INDEX_SCALE; public native long staticFieldOffset(Field field); public native int getIntVolatile(Object obj, long l); public native long getLong(Object obj, long l); public native int arrayBaseOffset(Class class1); public native int arrayIndexScale(Class class1); static { ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset([I); ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale([I); } }
4、CAS操作-Compare And Swap
Unsafe中除了objectFieldOffset(Field) 這個方法外,還有一個類似的方法staticFieldOffset(Field)。這兩個方法用於返回類定義中某個屬性在主存中設定的偏移量。請看以下代碼:
// 注意本代碼中的unsafe對象就是根據之前代碼獲取到的 // 開始使用unsafe對象,分別找到UserPojo對象中child屬性和name屬性的內存地址偏移量 // 首先是UserPojo類中的child屬性,在內存中設定的偏移位置 Field field = UserPojo.class.getDeclaredField("child"); // 這就是一旦這個類實例化后,該屬性在內存中的偏移位置 long offset = unsafe.objectFieldOffset(field); System.out.println("child offset = " + offset); // 然后是UserPojo類中的name屬性,在內存中設定的偏移位置 Field fieldName = UserPojo.class.getDeclaredField("name"); long nameOffset = unsafe.objectFieldOffset(fieldName); System.out.println("name offset = " + nameOffset);
測試結果為 打印對應各個對象屬性在主存中的偏移位置;具體結果不貼出來了;
Unsafe中除了compareAndSwapObject 這個方法外,還有兩個類似的方法:unsafe.compareAndSwapInt和unsafe.compareAndSwapLong。這些方法的作用就是對屬性進行比較並替換(俗稱的CAS過程——Compare And Swap)。當給定的對象中,指定屬性的值符合預期,則將這個值替換成一個新的值並且返回true;否則就忽略這個替換操作並且返回false。
請注意CAS過程是sun.misc.Unsafe類中除了獲取內存偏移量以外,提供的最重要的功能了——因為Java中很多基於“無同步鎖”方式的功能實現原理都是基於CAS過程。
方法舉例:CompareAndSwapInt()
/** * 比較obj的offset處內存位置中的值和期望的值,如果相同則更新。此更新是不可中斷的。 * * @param obj 需要更新的對象 * @param offset obj中整型field的偏移量 * @param expect 希望field中存在的值 * @param update 如果期望值expect與field的當前值相同,設置filed的值為這個新值 * @return 如果field的值被更改返回true */ public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
具體是代碼應用實例:
UserPojo user = new UserPojo(); user.setName("yinwenjie"); user.setSex(11); user.setUserId("userid"); // 獲得sex屬性的內存地址偏移量 Field field = UserPojo.class.getDeclaredField("sex"); long sexOffset = unsafe.objectFieldOffset(field); /* * 比較並修改值 * 1、需要修改的對象 * 2、要更改的屬性的內存偏移量 * 3、預期的值 * 4、設置的新值 * */ // 為什么是Object而不是int呢?因為sex屬性的類型是Integer不是int嘛 if(unsafe.compareAndSwapObject(user, sexOffset, 11, 13)) { System.out.println("更改成功!"); } else { System.out.println("更改失敗!"); }
首先創建一個UserPojo類的實例對象,這個實例對象有三個屬性name、sex和userId。接着我們找到sex屬性在主存中設定的偏移量sexOffset,並進行CAS操作。請注意compareAndSwapObject方法的四個值:第一個值表示要進行操作的對象user,第二個參數通過之前獲取的主存偏移量sexOffset告訴方法將要比較的是user對象中的哪個屬性,第三個參數為技術人員所預想的該屬性的當前值,第四個參數為將要替換成的新值。
那么將方法套用到以上的compareAndSwapObject執行過程中:如果當前user對象中sex屬性為11,則將這個sex屬性的值替換為13,並返回true;否則不替換sex屬性的值,並且返回false。
Unsafe.getAndAddInt(Object, long, int)和類似方法
類似的方法還有getAndAddLong(Object, long, long),它們的作用是利用Unsafe的原子操作性,向調用者返回某個屬性當前的值,並且緊接着將這個屬性增加一個新的值。在java.util.concurrent.atomic代碼包中,有一個類AtomicInteger,這個類用於進行基於原子操作的線程安全的計數操作,且這個類在JDK1.8+的版本中進行了較大的修改。以下代碼示例了該類的getAndIncrement()方法中的實現片段:
public class AtomicInteger extends Number implements java.io.Serializable { …… private volatile int value; …… private static final long valueOffset; …… // 獲取到value屬性的內存偏移量valueOffset static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } …… /** * 這是JDK1.8中的實現 * Atomically increments by one the current value. * @return the previous value */ public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
通過以上代碼的演示可以看到,AtomicInteger類中定義了一個value屬性,並通過unsafe.objectFieldOffset方法獲取到了這個屬性在主存中設定的偏移量valueOffset。接着就可以在getAndIncrement方法中直接使用unsafe.getAndAddInt的方式,通過偏移量valueOffset將value屬性的值加“1”。但是該方法的實現在JDK1.8之前的版本中,實現代碼卻是這樣的:
// 獲取偏移量valueOffset的代碼類似,這里就不再展示了 …… public final int getAndIncrement() { // 一直循環,直到 for (;;)
{ int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } …… public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
這里采用的For循環進行原子atomicinteger操作;后邊篇幅會寫java的自旋鎖以及自旋鎖的一種具體實現方式“樂觀鎖”,還會分析“樂觀鎖”適合使用的場景。
在以上代碼中,getAndIncrement方法內部會不停的循環,直到unsafe.compareAndSwapInt方法執行成功。但多數情況下,循環只會執行一次,因為多線程強占同一對象屬性的情況並不是隨時都會出現。
如果存在多線程進行訪問這段for循環的代碼 如果保證其結果是准確的呢,比如 100個線程執行 atomicinteger 的自增操作;
下面用結合一個圖來說明:
我們看到如果三個線程來訪問此基於樂觀鎖的代碼時,首先線程3 率先到達 if 的地方 進行判斷此時current的值為10,此時 其他的 線程2 線程3 都停留在get方法的地方 獲取到了對象屬性的值10
此時如果線程3 根據預期值進行判斷的過程中發現 current的值 與 對象屬性value的值是一樣的 此時會進行 CAS 操作,將對象的 value設置當前的 next 值 也就是 11; 然后線程3 執行結束 如果此時
線程2 獲得執行權限 繼續往下執行 此時線程1 的 current值 還是為 10 next的值為11 來到if的地方進行判斷 此時 因為對象的屬性value的值是11 與預期值 current不等 所以不行CAS操作; 然后繼續進行新一輪的
for循環 此時 繼續get取得 current的值 是 11 next的值我 12 此時進行if 測試的預期值和對象的value值是一樣的 然后執行更新操作;此時對象value的值被更新為12 線程2執行完畢,然后就剩下了線程 1 該線程的執行與線程2
的執行時一樣的 需要進行一次新的for循環 才可以實現更新值得操作;
從上面的過程可以看出,三個線程可以完成三次更新值得操作,並且沒有加入同步鎖。
在JDK1.8中Unsafe還有一些其它實用的原子操作方法:
-
PutXXXXX(Object, long, short)
類似的方法包括:putInt(Object, long, int)、putBoolean(Object, long, boolean)、putShort(Object, long, short)、putChar(Object, long, char)、putDouble(Object, long, double)等,這些都是針對指定對象中在偏移量上的屬性值,進行直接設定。這些操作發生在CPU一級緩存(L1) 或者二級緩存(L2)中,但是這些方法並不保證工作在其它內核上的線程“立即看到”最新的屬性值。
-
putXXXXXVolatile(Object, long, byte)
類似的方法包括:putByteVolatile(Object, long, byte)、putShortVolatile(Object, long, short)、putFloatVolatile(Object, long, float)、putDoubleVolatile(Object, long, double)等等,這些方法的主要作用雖然也是直接針對偏移量改變指定對象中的屬性值,但是這些方法保證工作在其它內核上的線程能“立即看到”最新的屬性值——也就是說這些方法滿足volatile語義(后續文章會詳細介紹volatile的詳細工作原理)
總結:
Unsafe類的總結已經寫完 ,基於該類使用的類有很多,除了原子數據 AtomicXXX, 還有LockSupport類 以及在 線程池 ThreadPool 類也是用了該類, 后邊具體寫這兩個類。