博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Unsafe.putOrderedXXX系列方法详解(数组赋值的第二种方式)
阅读量:3968 次
发布时间:2019-05-24

本文共 3057 字,大约阅读时间需要 10 分钟。

在Netty中,IO线程用于存储任务的容器是MpscUnboundedArrayQueue类.

所有对外的读写操作,都’委托’给IO线程来执行,非IO线程(比如业务线程)若要写数据,必须将写操作封装成一个任务,提交到IO线程的任务队列中.IO线程会择机执行任务队列中的任务,将数据写入到网络(实际只是写到TCP缓冲区). 那么这个任务队列就很重要了,它必须是高性能的. 在Netty以前的版本中,使用JDK的BlockingQueue实现这个任务队列,而Netty是一个追求性能极致的框架(不被规则和常规所束缚),目前选择jctools这个工具包中的MpscUnboundedArrayQueue类实现任务队列.

这个MpscUnboundedArrayQueue队列很特别,它是数组和链表的结合.但是它不是哈希,它的结构类似下面这样

MpscUnboundedArrayQueue结构.png

同等大小的数组之间通过链表方式连接.

MpscUnboundedArrayQueue
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(); } }}, "生产者-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(); } }}, "生产者-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(); } }}, "生产者-3").start();

以上3个生产者向队列中’生产’数据,过了一会,通过dump堆信息.再通过Eclipse MAT查看堆信息.

dump文件.png

如上图,展开数组,可以看到它的结构是一个数组’链’一个数组.

当非IO线程提交任务的时候,就需要向数组中存储元素值.那么它是如何给数组赋值的呢?

一般情况,给数组赋值如下

int[] arr = new int[8];arr[3] = 9527;

而MpscUnboundedArrayQueue类中,是通过Unsafe.putOrderedXXX系列的方法,给数组赋值的.

如并发中的CAS, 内存申请, 线程阻塞和解除阻塞park/unpark, 以及putOrderedXXX等等, 都是Unsafe类提供的方法.

在此之前,先研究下对象的内存布局.

面试题: Object obj = new Object()在内存中占用多少字节?

Java对象是由markword,类型指针,数组长度(如果对象是数组的话),实例数据,对齐空间等组成.如下图

对象内存布局.png

我们以数组为例

int[] arr = new int[7];

通过代码验证, int[]数组的内存布局情况.

依赖的包

org.openjdk.jol
jol-core
0.9
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()); }}

同时设置虚拟机参数 -XX:+UseCompressedClassPointers ,表示启用压缩类指针, 比如在64位系统上,一个指针占用8字节,启用这个参数之后,指针占用4字节.

打印结果如下

对象内存布局.png

根据内存布局,我们可以知道数组的第一个元素距离起始位置4+4+4+4 = 16个字节.

类型.png

如上图,[I表示int数组含义,具体映射关系,可以直接参数OpenJDK源码.

类型的源码.png

在Unsafe类中有个arrayBaseOffset方法,就是用来返回数组中第一个元素的偏移地址,验证代码如下.

arrayBaseOffset.png

输出16,与上面查看对象内存布局的结果是一致的.

如果要修改数组中的某一个元素,要知道3个值

1.数组首地址
2.第一个元素偏移地址
3.每个元素的大小

在学习C语言的时候,经常萦绕耳边的一句话是’数组名表示数组首地址’, 在Java中,数组名也是数组首地址.

通过unsafe.arrayBaseOffset可以获取第一个元素偏移地址.
在Java中,int占4字节,double占8字节等等.通过unsafe.arrayIndexScale方法可以获取数组中元素占用的大小,代码如下.

类型大小.png

通过以上的分析,最后得到如下一张图.

数组.png

有了数组首地址,第一个元素偏移地址和元素大小,就可以指向数组的任意元素的地址,也就可以修改元素了.

有了以上的基础知识,接下来就是本文要说的方法Unsafe.putOrderedXXX(…).

putOrderedXXX.png

如上图,通过unsafe.putOrderedXXX(…)给第2个元素赋值450.

unsafe.putOrderedLong(a, offset, 450);

第一个参数a是数组的名称,即数组首地址, 第二个参数offset是计算得到,即第二个元素距离首地址的偏移地址, 第三个元素450是要赋的值.

在jctools工具类中的org.jctools.queues.BaseMpscLinkedArrayQueue#offer方法就是向数组中存储元素的核心方法,它如何存储元素的核心代码如下.

图片.png

调用的方法就是unsafe.putOrderedXXX(…).


公众号

在这里插入图片描述

转载地址:http://upcki.baihongyu.com/

你可能感兴趣的文章
一个用户空间读取输入事件的例子
查看>>
输入事件的传递过程
查看>>
输入事件的传递过程
查看>>
输入子系统设备模型分析
查看>>
输入子系统设备模型分析
查看>>
USB驱动程序之描述符
查看>>
USB驱动程序之描述符
查看>>
一般的,在s3c2440中,要想进行dma…
查看>>
一般的,在s3c2440中,要想进行dma…
查看>>
2011年05月29日
查看>>
2011年05月29日
查看>>
2011年05月29日
查看>>
2011年05月29日
查看>>
ARM&nbsp;Linux中断机制之中断的初始化
查看>>
ARM&nbsp;Linux中断机制之中断的初始化
查看>>
USB驱动之描述符
查看>>
USB系统设备模型建立流程
查看>>
DMA原理
查看>>
USB系统设备模型建立流程
查看>>
杂项设备实现原理
查看>>