首页 > 其他分享 >JDK9 - VarHandle小记

JDK9 - VarHandle小记

时间:2024-01-19 15:59:06浏览次数:30  
标签:变量 fence globalSharedObject 访问 JDK9 volatile VarHandle 小记

说在前面

在开始之前,有必要点明一下虽只字未提但贯穿全文的核心,从而知道我们使用某些API的目的是什么:VarHandle/Unsafe提供了比volatile关键字更弱的变量访问方式,合理地利用它们可以让我们程序可以在符合运行预期的话情况下提高性能,这里的“弱”指的是约束更少。

所谓约束,举个例子,我们在多线程共享变量时,完全不加同步/互斥约束的话可能会导致程序出错,但如果加的约束过头了,比如每个线程访问共享变量时都套一把大锁,又会极大降低并发量。

在此引用大神Doug Lea的一句话,摘自参考链接中的视频:

"Some programmers over synchronize code, which can make programs slow, some programmers under synchronize code, which can make programs wrong"

- Doug Lea

四种访问模式 + volatile

四种访问模式从弱到强依次为plain, opaque, release/acuqire。如果加上约束最强的volatile读写的话,应该算是五种访问模式,下面依次介绍。

  • plain:就是普通的变量访问,在保证单线程最终一致性的情况下允许指令随意的重排序、最多同时32位可以原子访问

    // X为公共变量x的VarHandle对象,x初始化为0
    // Thread1
    while (X.get(this) != 1); // 永远观察不到x被赋值为1
    
    // Thread2
    X.set(this, 1)
    
  • opaque:在plain的基础上,更新是coherent的、对于所有类型的访问都是原子的(比如64bits的long类型)。如何理解这个coherent呢,从现象上来说就是线程能观测到别的线程对共享变量的修改,比如上面例子中,opaque模式访问x最终会观测到1。(另外再提一嘴为什么这种模式命名为opaque,与transparent这个词比较来说:transparent表示计算机中存在这样的特性/功能,但是我们感知不到;opaque表示我们能感知到,也能去显式地使用,但是我们不知道内部机制,类似黑盒。)

  • release(write) / acuqire(read):release指的是写,acquire指的是读。在opaque的基础上,release write之前的访问都不会被重排序到release write之后,acquire read之后的访问都不会被排序到acquire read之前。

    // y为普通变量,X为公共变量x的VarHandle对象,x初始化为0
    // Thread1
    y=2;
    X.setRelease(this, 1); // 在运行这句之前,y已经被赋值为2
    
    // Thread2
    if (X.getAcquire(this)==1) {
      r1 = y; // 在运行这句之前,x已经保证为1
    }
    
  • volatile:volatile前后的访问都不能重排序,VarHandle支持volatile读写,与直接使用Java在语法层面提供的volatile关键字效果相同

Fences

相比绑定到某个变量进行访问,VarHandle还支持直接加访问屏障来实现相同效果,更加灵活。VarHandle提供了五种屏障,按从弱到强:

  • LoadLoadFence:fence前后的读不能重排
  • StoreStoreFence:fence前后的写不能重排
  • ReleaseFence:fence前的读写和fence后的写不能重排
  • AcquireFence:fence前的读和fence后的读写不能重排
  • FullFence:fence前后的读写不能重排

下面就举个例子看看访问模式和屏障的应用,以及什么时候该用谁:

// 分别用访问模式和屏障来实现:初始化x并赋值到全局变量,并且保证其它线程不会读到未完全初始化的x,即“赋值到全局变量”不会与“初始化x”重排

// 方案1. 使用访问模式实现
x.a = 1;
x.b = 2;
VarHandle.releaseFence();
globalSharedObject.x = x;

// 方案2. 使用屏障实现(X为globalSharedObject.x的VarHandle)
x.a = 1;
x.b = 2;
X.setRelease(globalSharedObject, x);

只有单个变量需要同步访问的话,使用访问模式API确实更加方便些,但如果是多个变量的话,直接用屏障则看起来更加方便(运行效率哪个更好未知):

// 初始化多个对象:x, y, z

// 方案1. 使用屏障实现
x.a = 1;
y.a = 1;
z.a = 1;
VarHandle.releaseFence();
globalSharedObject.x = x;
globalSharedObject.y = y;
globalSharedObject.z = z;

// 方案2. 使用访问模式实现(X为globalSharedObject.x的VarHandle)
x.a = 1;
X.setRelease(globalSharedObject, x);
y.a = 1;
Y.setRelease(globalSharedObject, y);
z.a = 1;
Z.setRelease(globalSharedObject, z);

回到上面单个变量x的同步访问例子,可以观察到“初始化x”这个动作只是将一堆字面量赋值到x上,相当于都是对x的写操作,并没有读操作。因此在使用屏障的实现中,可以将releaseFence退化为更约束更弱的storeStoreFence:

x.a = 1;
x.b = 2;
VarHandle.storeStoreFence();
globalSharedObject.x = x;

参考链接

「StackOverflow」What's the difference between getVolatile and getAcquire?

「YouTube」Java 9 VarHandles Best practices, and why? by Tobi Ajila

Using JDK 9 Memory Order Modes - Doug Lea

标签:变量,fence,globalSharedObject,访问,JDK9,volatile,VarHandle,小记
From: https://www.cnblogs.com/nosae/p/17974817

相关文章

  • SA&SAM 小记
    0.Front纯笔记,不含教学内容,部分有拓展,部分太简单所以以”显然“带过了,总结了部分oi-wiki的内容。字符串为\(S\),长度为\(n\),且应有\(|\Sigma|\len\)。通常来说,大写字母表示为字符串,小写字母表示为字符。后缀的编号为\(i\),表示是以\(i\)为起点的后缀。基础小练习1.......
  • 【小记】BITMAP To BMP 调用 GetDIBits 引发栈内存损坏问题
    BITMAPbitmap;if(!GetObject(hBitmap,sizeof(bitmap),&bitmap)){//外部传入hBitmapreturnfalse;}//创建位图信息头BITMAPINFObitInfo;BITMAPINFOHEADER&bi=bitInfo.bmiHeader;bi.biWidth=bitmap.bmWidth;bi.biHeight=bitmap.bmHeight;bi.biPlane......
  • Android架构测试 套小记
    Android架构测试主要是为了确保Android应用程序在不同设备和系统版本上的兼容性、性能和稳定性。这需要对应用程序的各个组件进行测试,包括活动、服务、广播接收器、内容提供程序等。以下是进行Android架构测试时可以采取的一些步骤:单元测试:对应用程序的各个组件进行测试,确保它......
  • WPF的DataGrid绑定DataTable调研小记
    公司有个项目,界面很卡,同事怀疑是DataTable刷新引起的,我写了一个小Demo测试一下这块的性能。测试的结果DataTalbe的绑定非常的耗时我的前台代码:<DataGridGrid.Row="1"AutoGenerateColumns="True"BorderBrush="LightGray"ItemsSource="{BindingItems}"......
  • 一些小记
    美剧:艾米丽在巴黎 刘瑜观念的水位李银河:女性主义《看见成长的自己》 复旦大学沈奕裴老师讲座:是什么阻挡了我们相亲相爱张悦然顿悟的时刻纪录片河西走廊、神秘的西夏博尔赫斯诗我用什么才能留住你黄灿然奇迹集樊登解读:恰如其分的自尊人生有很多象限。很多......
  • 二叉树解题思路小记
    二叉树前中后序位置代码框架voidtraverse(TreeNoderoot){if(root==null){return;}//前序位置traverse(root.left);//中序位置traverse(root.right);//后序位置}代码写在不同的位置,只是执行的时间不同。前序位置刚刚进入二......
  • JDK9中的String底层实现为什么用UTF-16而不用UTF-8呢?
    UTF-8是一种对空间利用效率最高的编码集,它是不定长的,使用1~4字节为每个字符编码。这种情况下,如果能用一个字节存放字符就不会使用两个字节,两个字节不够就用三个字节。这种编码集只适用于传输和存储,并不适合拿来做String的底层实现。String有随机访问的方法,比如charAt、subString等......
  • 2023-2024元旦联欢会小记
    Day-2gg说放假,终于能确定回来了。Day-1开始摆烂,但是还是在学习淀粉质。怎么说看了付姐的朋友圈,看到大家在包饺子,又错过一个活动怎么说。gg说开茶话会。高一同学:茶话会?不,是鸿门宴。真的是晚会!唱了首《稻香》。感觉回到了高一在班里一起唱歌。晚会在情侣合体的时候达到了......
  • 【2023.12.29】修复服务器小记录,重装Proxmox
    半年没碰服务器了,没想到还是挂了,卡在BIOS过不去NUC因为没有主板电池,所以还特地找了下怎么重置,没想到是拔出主板上的黄色保护器,使两个针脚空接和我想象中的不太一样,照理来说应该是针脚对接,才能重置才对因为这样子的话,这个黄色保护套就不能随意丢弃了,感觉这个主板的设计有问题折......
  • 【反射】反射获取私有字段小记
    问题://直接按类字面量获取Class<?>myClass=ClassTestA.class;//全类名反射获取Class<?>myClass=Class.forName("com.cambrianwenjie.demo.ClassTestA");//获取私有字段FieldprivateField=myClass.getDeclaredField("name");//设置私有字段可访问priva......