極客們,請收下2021 微軟 x 英特爾黑客松大賽英雄帖!>>>

在Netty中,IO線程用于存儲任務的容器是MpscUnboundedArrayQueue類. 所有對外的讀寫操作,都'委托'給IO線程來執行,非IO線程(比如業務線程)若要寫資料,必須将寫操作封裝成一個任務,送出到IO線程的任務隊列中.IO線程會擇機執行任務隊列中的任務,将資料寫入到網絡(實際隻是寫到TCP緩沖區). 那麼這個任務隊列就很重要了,它必須是高性能的. 在Netty以前的版本中,使用JDK的BlockingQueue實作這個任務隊列,而Netty是一個追求性能極緻的架構(不被規則和正常所束縛),目前選擇jctools這個工具包中的MpscUnboundedArrayQueue類實作任務隊列.
這個MpscUnboundedArrayQueue隊列很特别,它是數組和連結清單的結合.但是它不是哈希,它的結構類似下面這樣
同等大小的數組之間通過連結清單方式連接配接.
以上3個生産者向隊列中'生産'資料,過了一會,通過dump堆資訊.再通過Eclipse MAT檢視堆資訊.
如上圖,展開數組,可以看到它的結構是一個數組'鍊'一個數組.
當非IO線程送出任務的時候,就需要向數組中存儲元素值.那麼它是如何給數組指派的呢?
一般情況,給數組指派如下
而MpscUnboundedArrayQueue類中,是通過Unsafe.putOrderedXXX系列的方法,給數組指派的.
如并發中的CAS, 記憶體申請, 線程阻塞和解除阻塞park/unpark, 以及putOrderedXXX等等, 都是Unsafe類提供的方法.
在此之前,先研究下對象的記憶體布局.
面試題: Object obj = new Object()在記憶體中占用多少位元組?
Java對象是由markword,類型指針,數組長度(如果對象是數組的話),執行個體資料,對齊空間等組成.如下圖
我們以數組為例
通過代碼驗證, int[]數組的記憶體布局情況.
依賴的包
同時設定虛拟機參數 -XX:+UseCompressedClassPointers ,表示啟用壓縮類指針, 比如在64位系統上,一個指針占用8位元組,啟用這個參數之後,指針占用4位元組.
列印結果如下
根據記憶體布局,我們可以知道數組的第一個元素距離起始位置4+4+4+4 = 16個位元組.
如上圖,[I表示int數組含義,具體映射關系,可以直接參數OpenJDK源碼.
在Unsafe類中有個arrayBaseOffset方法,就是用來傳回數組中第一個元素的偏移位址,驗證代碼如下.
輸出16,與上面檢視對象記憶體布局的結果是一緻的.
如果要修改數組中的某一個元素,要知道3個值 1.數組首位址 2.第一個元素偏移位址 3.每個元素的大小
在學習C語言的時候,經常萦繞耳邊的一句話是'數組名表示數組首位址', 在Java中,數組名也是數組首位址. 通過unsafe.arrayBaseOffset可以擷取第一個元素偏移位址. 在Java中,int占4位元組,double占8位元組等等.通過unsafe.arrayIndexScale方法可以擷取數組中元素占用的大小,代碼如下.
通過以上的分析,最後得到如下一張圖.
有了數組首位址,第一個元素偏移位址和元素大小,就可以指向數組的任意元素的位址,也就可以修改元素了.
有了以上的基礎知識,接下來就是本文要說的方法Unsafe.putOrderedXXX(...).
如上圖,通過unsafe.putOrderedXXX(...)給第2個元素指派450.
第一個參數a是數組的名稱,即數組首位址, 第二個參數offset是計算得到,即第二個元素距離首位址的偏移位址, 第三個元素450是要賦的值.
在jctools工具類中的org.jctools.queues.BaseMpscLinkedArrayQueue#offer方法就是向數組中存儲元素的核心方法,它如何存儲元素的核心代碼如下.
調用的方法就是unsafe.putOrderedXXX(...).