Leader:這樣的 Bug 你也寫的出來?

Hello~各位讀者新年好!不知道大家春節假期是否已延長,小黑哥剛接到通知,假期延長到 2 月 2 號,另外回去之後需要在家辦公,自行隔離兩週。還沒試過在家辦公,小黑哥就怕到時候生物鐘還沒調整過來,一覺睡醒已經是下午了。。。


Leader:這樣的 Bug 你也寫的出來?


前言

春節假期,還躺在床上小黑哥,收到對賬系統的一條預警短信,提示當前系統資金核對存在問題。關於資金的問題,都是大問題,小黑哥連忙拔出電腦,連上 VPN,登錄生產環境的查看相關日誌。

通過日誌,很快小黑哥定位到相關代碼。


Leader:這樣的 Bug 你也寫的出來?


有的同學可能一下子就能看出這裡的問題, Long 對象採用 !=進行比較,這真是一個低級 Bug。幸好 Leader 還不知道,趕緊悄悄修復一下。


Leader:這樣的 Bug 你也寫的出來?


現在回想小黑哥當初寫這段代碼的時候,誤以為兩個 Long 對象比較將會進行自動拆箱,轉變為兩個基本數值類型比較。

下面開始複習一下 Java 自動裝箱與拆箱機制。

自動裝箱與拆箱機制

自動裝箱(Autoboxing),是 JDK5 新增的一種語法糖,將會在代碼編譯時自動將原始類型轉換為其對應的對象包裝器類。例如將 int 轉換為 Integer,double 轉換為 Double。如果轉換結果相反,我們就將其稱為拆箱。

下面是一個自動裝箱的例子:


Leader:這樣的 Bug 你也寫的出來?


上面代碼 li.add(i) 就發生自動裝箱,將基本數據類型 long 轉換為其包裝類 Long。

查看這段代碼對應的字節碼。


Leader:這樣的 Bug 你也寫的出來?


圖上黃線標註的字節碼對應的代碼為 li.add(i)。從這我們可以看到 long 類型的自動裝箱實際上調用 Long#valueOf 方法。所以編譯器運行時將之前的代碼轉換為下面的代碼


Leader:這樣的 Bug 你也寫的出來?


接下來我們來看一個自動拆箱的例子:


Leader:這樣的 Bug 你也寫的出來?


由於 Long 包裝類對象不能用於求模(%)以及 +=,所以這段代碼將會發生自動拆箱,將 Long 轉化為 long 類型。相應的這裡我們也看下其編譯之後的字節碼。


Leader:這樣的 Bug 你也寫的出來?


這裡的字節碼比之前的複雜很多,這裡主要關注黃線部分。可以看到這裡調用 Long#longValue 將 Long 對象轉為 long 類型。所以自動拆箱這個例子,最後編譯器生成字節碼等同於以下代碼:


Leader:這樣的 Bug 你也寫的出來?


Java 規定的 8 種數據類型都有其對應包裝類,這些都可以進行相應的自動裝箱和拆箱。


Leader:這樣的 Bug 你也寫的出來?


這裡小結一下:

  1. 自動裝箱機制是通過調用包裝器類 valueOf 實現
  2. 自動拆箱機制通過調用包裝器類的相應的 **Value ,如 longValue, intValue 實現

Cache 陷阱

自動裝箱和拆箱概念說起其實挺簡單的,但是如果使用不當可能就會踩坑。

我們來看一段代碼:


Leader:這樣的 Bug 你也寫的出來?


如果你對上面的結果的不是很清楚,恭喜你,暖男小黑哥幫你排雷了。

上面輸出結果為:

<code>true
false/<code>

這裡輸出結果之所以為這樣,主要與 LongCache 有關。自動裝箱進制將會調用 Long#valueOf,其源碼如下:


Leader:這樣的 Bug 你也寫的出來?


只要數值範圍位於 [-128,127] 之間,valueOf 就會返回 LongCache.cache 這個數組中的值。所以 aLong/bLong其實是同一個對象,cLong/dLong 是兩個不用對象的。

Long#valueOf 這個方法通過 Cache 減少創建對象的數量,提高相應的空間以及時間性能。


Leader:這樣的 Bug 你也寫的出來?


至於為什麼緩存 [-128,127] 之間的數字,而沒有緩存更多的值,甚至緩存所有的值?

這是因為 Long 範圍為**[-2^63,2^63]**,總共 2^64 ,這麼多對象實例,顯然不宜全部緩存。至於 [-128,127] 之間數字 JDK 設計者認為這些數字使用頻率高,個人認為這是一個經驗值。

另外這個 cache可能會導致另外一個問題:鎖共用。

Leader:這樣的 Bug 你也寫的出來?

上面代碼看起來 A 類使用 along 這個對象鎖,而 B 類使用 blong 這個對象鎖,看起來兩個風馬牛不相及,但是實際上兩個對象由於 Cache 機制,導致其實際上使用同一個對象,A 與 B 共用同一把鎖。

除了 Long#valueOf 方法中存在 Cache 以外,Byte,Short,Integer,Character 也存在相應的 Cache 值。其中 Integer 對象可以通過 -XX:AutoBoxCacheMax=<size> 改變 Cache 初始範圍。/<size>

技術總結

通過自動裝箱與拆箱機制,大大減少了數據類型轉化的代碼,但是同時帶來一些隱藏的問題。這裡我們需要記住:

  1. 所有對象之間不能使用 == 比較,需要使用 equals 代替,推薦使用 JDK7 提供的 Objects#equals 方法,該方法可以有效避免比較過程空指針問題
  2. 基本數據類型的包裝類不適合當做鎖對象

各位讀者朋友們,請牢記這兩個總結,不要踩中這兩個坑哦。另外推薦大家安裝一下 FindBugs 這個插件,通過這個插件掃描代碼,可以找出代碼中的相關缺陷。上面兩個問題,通過 FindBugs都能掃描出來,簡直神器。

對象比較問題:


Leader:這樣的 Bug 你也寫的出來?

鎖問題

Leader:這樣的 Bug 你也寫的出來?

鏈接:https://juejin.im/post/5e2fdb6f6fb9a02fb85aed1e


分享到:


相關文章: