Unsafe.putOrderedXXX系列方法詳解(數(shù)組賦值的第二種方式)

同等大小的數(shù)組之間通過鏈表方式連接.
MpscUnboundedArrayQueue<Integer> queue = new MpscUnboundedArrayQueue<>(4);new Thread(() -> {while (true) {try {for (int i = 1; i < 100; i++) {Thread.sleep(3000);queue.offer(i);}} catch (InterruptedException e) {e.printStackTrace();}}}, "生產(chǎn)者-1").start();new Thread(() -> {while (true) {try {for (int i = 200; i < 300; i++) {Thread.sleep(2000);queue.offer(i);}} catch (InterruptedException e) {e.printStackTrace();}}}, "生產(chǎn)者-2").start();new Thread(() -> {while (true) {try {for (int i = 300; i < 400; i++) {Thread.sleep(1000);queue.offer(i);}} catch (InterruptedException e) {e.printStackTrace();}}}, "生產(chǎn)者-3").start();
以上3個生產(chǎn)者向隊列中'生產(chǎn)'數(shù)據(jù),過了一會,通過dump堆信息.再通過Eclipse MAT查看堆信息.

如上圖,展開數(shù)組,可以看到它的結(jié)構(gòu)是一個數(shù)組'鏈'一個數(shù)組.
當(dāng)非IO線程提交任務(wù)的時候,就需要向數(shù)組中存儲元素值.那么它是如何給數(shù)組賦值的呢?
一般情況,給數(shù)組賦值如下
int[] arr = new int[8];arr[3] = 9527;
而MpscUnboundedArrayQueue類中,是通過Unsafe.putOrderedXXX系列的方法,給數(shù)組賦值的.
如并發(fā)中的CAS, 內(nèi)存申請, 線程阻塞和解除阻塞park/unpark, 以及putOrderedXXX等等, 都是Unsafe類提供的方法.
在此之前,先研究下對象的內(nèi)存布局.
面試題
Object obj = new Object()在內(nèi)存中占用多少字節(jié)?
Java對象是由markword,類型指針,數(shù)組長度(如果對象是數(shù)組的話),實例數(shù)據(jù),對齊空間等組成.如下圖

我們以數(shù)組為例
int[] arr = new int[7];通過代碼驗證, int[]數(shù)組的內(nèi)存布局情況.
依賴的包
<dependency><groupId>org.openjdk.jol</groupId><artifactId>jol-core</artifactId><version>0.9</version></dependency>
import org.openjdk.jol.info.ClassLayout;public class Example {public static void main(String[] args) {int[] arr = new int[7];System.out.println(ClassLayout.parseInstance(arr).toPrintable());}}
同時設(shè)置虛擬機(jī)參數(shù) -XX:+UseCompressedClassPointers ,表示啟用壓縮類指針, 比如在64位系統(tǒng)上,一個指針占用8字節(jié),啟用這個參數(shù)之后,指針占用4字節(jié).
打印結(jié)果如下

根據(jù)內(nèi)存布局,我們可以知道數(shù)組的第一個元素距離起始位置4+4+4+4 = 16個字節(jié).

如上圖,[I表示int數(shù)組含義,具體映射關(guān)系,可以直接參數(shù)OpenJDK源碼.

在Unsafe類中有個arrayBaseOffset方法,就是用來返回數(shù)組中第一個元素的偏移地址,驗證代碼如下.

輸出16,與上面查看對象內(nèi)存布局的結(jié)果是一致的.
如果要修改數(shù)組中的某一個元素,要知道3個值
1.數(shù)組首地址
2.第一個元素偏移地址
3.每個元素的大小
在學(xué)習(xí)C語言的時候,經(jīng)??M繞耳邊的一句話是'數(shù)組名表示數(shù)組首地址', 在Java中,數(shù)組名也是數(shù)組首地址.
通過unsafe.arrayBaseOffset可以獲取第一個元素偏移地址.
在Java中,int占4字節(jié),double占8字節(jié)等等.通過unsafe.arrayIndexScale方法可以獲取數(shù)組中元素占用的大小,代碼如下.

通過以上的分析,最后得到如下一張圖.

有了數(shù)組首地址,第一個元素偏移地址和元素大小,就可以指向數(shù)組的任意元素的地址,也就可以修改元素了.
有了以上的基礎(chǔ)知識,接下來就是本文要說的方法Unsafe.putOrderedXXX(...).

如上圖,通過unsafe.putOrderedXXX(...)給第2個元素賦值450.
unsafe.putOrderedLong(a, offset, 450);第一個參數(shù)a是數(shù)組的名稱,即數(shù)組首地址, 第二個參數(shù)offset是計算得到,即第二個元素距離首地址的偏移地址, 第三個元素450是要賦的值.
在jctools工具類中的org.jctools.queues.BaseMpscLinkedArrayQueue#offer方法就是向數(shù)組中存儲元素的核心方法,它如何存儲元素的核心代碼如下.

調(diào)用的方法就是unsafe.putOrderedXXX(...).
