使用 Random 類時(shí),為了避免重復(fù)創(chuàng)建的開銷,我們一般將實(shí)例化好的 Random 對(duì)象設(shè)置為我們所使用服務(wù)對(duì)象的屬性或靜態(tài)屬性,這在線程競(jìng)爭(zhēng)不激烈的情況下沒有問題,但在一個(gè)高并發(fā)的 web 服務(wù)內(nèi),使用同一個(gè) Random 對(duì)象可能會(huì)導(dǎo)致線程阻塞。
Random 的隨機(jī)原理是對(duì)一個(gè)”隨機(jī)種子”進(jìn)行固定的算術(shù)和位運(yùn)算,得到隨機(jī)結(jié)果,再使用這個(gè)結(jié)果作為下一次隨機(jī)的種子。在解決線程安全問題時(shí),Random 使用 CAS 更新下一次隨機(jī)的種子,可以想到,如果多個(gè)線程同時(shí)使用這個(gè)對(duì)象,就肯定會(huì)有一些線程執(zhí)行 CAS 連續(xù)失敗,進(jìn)而導(dǎo)致線程阻塞。
在我們的常識(shí)里,get 方法是最容易拋異常的地方,比如空指針、類型轉(zhuǎn)換等,但 Unsafe.getLong() 方法是個(gè)非常安全的方法,它從某個(gè)內(nèi)存位置開始讀取四個(gè)字節(jié),而不管這四個(gè)字節(jié)是什么內(nèi)容,總能成功轉(zhuǎn)成 long 型,至于這個(gè) long 型結(jié)果是不是跟業(yè)務(wù)匹配就是另一回事了。而 set 方法也是比較安全的,它把某個(gè)內(nèi)存位置之后的四個(gè)字節(jié)覆蓋成一個(gè) long 型的值,也幾乎不會(huì)出錯(cuò)。
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { // Unsafe 設(shè)置了構(gòu)造方法私有,getUnsafe 獲取實(shí)例方法包私有,在包外只能通過反射獲取 Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); // Test 類是一個(gè)隨手寫的測(cè)試類,只有一個(gè) String 類型的測(cè)試類 Test test = new Test(); test.ttt = "12345"; unsafe.putLong(test, 12L, 2333L); System.out.println(test.value); }
運(yùn)行上面的代碼會(huì)得到一個(gè) fatal error,報(bào)錯(cuò)信息為 “A fatal error has been detected by the Java Runtime Environment: … Process finished with exit code 134 (interrupted by signal 6: SIGABRT)”。
可以從報(bào)錯(cuò)信息中看到虛擬機(jī)因?yàn)檫@個(gè) fatal error abort 退出了,原因也很簡(jiǎn)單,我使用 unsafe 將 Test 類 value 屬性的位置設(shè)置成了 long 型值 2333,而當(dāng)我使用 value 屬性時(shí),虛擬機(jī)會(huì)將這一塊內(nèi)存解析為 String 對(duì)象,原 String 對(duì)象對(duì)象頭的結(jié)構(gòu)被打亂了,解析對(duì)象失敗拋出了錯(cuò)誤,更嚴(yán)重的問題是報(bào)錯(cuò)信息中沒有類名行號(hào)等信息,在復(fù)雜項(xiàng)目中排查這種問題真如同大海撈針。
另一個(gè)疑問是我看到 Unsafe.objectFieldOffset 可以獲取到屬性在對(duì)象內(nèi)存的偏移量后,自己在 IDEA 里使用 main 方法試了上文中提到的 Test 類,發(fā)現(xiàn) Test 類的唯一一個(gè)屬性 value 相對(duì)對(duì)象內(nèi)存的偏移量是 12,于是比較疑惑這 12 個(gè)字節(jié)的組成。