在我的前一篇文章<伪共享和缓存行填充,从Java 6, Java 7 到Java 8>中, 我们演示了在Java 8中,可以采用@Contended在类级别上的注释,来进行缓存行填充。这样,多线程情况下的伪共享冲突问题。 感兴趣的同学可以查看该文。

其实,@Contended注释还可以应用于字段级别(Field-Level),当应用于字段级别时,被注释的字段将和其他字段隔离开来,会被加载在独立的缓存行上。在字段级别上,@Contended还支持一个“contention group”属性(Class-Level不支持),同一group的字段们在内存上将是连续,但和其他他字段隔离开来。

上面只是泛泛的介绍一下。关于@Contended应用于Field-Level特别是contention group的相关的资料很少,源代码中的注释中有一些,还有关于JEP-142(即关于增加@Contended的提议)的邮件讨论组中的描述(http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html),其中的讲解是非常详细的(由于该讨论发生在@Contended实现的最初阶段,不能保证和现在的实现完全一致), 我摘录和翻译如下:

@Contended注释的行为如下所示:

A,在类上应用Contended:

@Contendedpublic static class ContendedTest2 {private Object plainField1;private Object plainField2;private Object plainField3;private Object plainField4;}

将使整个字段块的两端都被填充:(以下是使用 –XX:+PrintFieldLayout的输出)(翻译注:注意前面的@140表示字段在类中的地址偏移)

TestContended$ContendedTest2: field layoutEntire class is marked contended@140 --- instance fields start ---@140 "plainField1" Ljava.lang.Object;
     @144 "plainField2" Ljava.lang.Object;
     @148 "plainField3" Ljava.lang.Object;
     @152 "plainField4" Ljava.lang.Object;
     @288 --- instance fields end ---@288 --- instance ends ---

注意,我们使用了128bytes的填充 -- 2倍于大多数硬件缓存行的大小 -- 来避免相邻扇区预取导致的伪共享冲突。

B,在字段上应用Contended:

public static class ContendedTest1 {@Contendedprivate Object contendedField1;private Object plainField1;private Object plainField2;private Object plainField3;private Object plainField4;}

将导致该字段从连续的字段块中分离开来并高效的添加填充:

TestContended$ContendedTest1: field layout@ 12 --- instance fields start ---@ 12 "plainField1" Ljava.lang.Object;
     @ 16 "plainField2" Ljava.lang.Object;
     @ 20 "plainField3" Ljava.lang.Object;
     @ 24 "plainField4" Ljava.lang.Object;
     @156 "contendedField1" Ljava.lang.Object; (contended, group = 0)@288 --- instance fields end ---@288 --- instance ends ---

C, 注解多个字段使他们分别被填充:

public static class ContendedTest4 {@Contendedprivate Object contendedField1;@Contendedprivate Object contendedField2;private Object plainField3;private Object plainField4;}

被注解的2个字段都被独立地填充:

TestContended$ContendedTest4: field layout@ 12 --- instance fields start ---@ 12 "plainField3" Ljava.lang.Object;
     @ 16 "plainField4" Ljava.lang.Object;
     @148 "contendedField1" Ljava.lang.Object; (contended, group = 0)@280 "contendedField2" Ljava.lang.Object; (contended, group = 0)@416 --- instance fields end ---@416 --- instance ends ---

在有些cases中,你会想对字段进行分组,同一组的字段会和其他字段有访问冲突,但是和同一组的没有。例如,(同一个线程的)代码同时更新2个字段是很常见的情况。如果同时把2个字段都添加@Contended注解是足够的(翻译注:但是太足够了),但我们可以通过去掉他们之间的填充,来优化它们的内存空间占用。为了区分组,我们有一个参数“contention group”来描述:

所以:

public static class ContendedTest5 {@Contended("updater1")private Object contendedField1;@Contended("updater1")private Object contendedField2;@Contended("updater2")private Object contendedField3;private Object plainField5;private Object plainField6;}

内存布局是:

TestContended$ContendedTest5: field layout@ 12 --- instance fields start ---@ 12 "plainField5" Ljava.lang.Object;@ 16 "plainField6" Ljava.lang.Object;@148 "contendedField1" Ljava.lang.Object; (contended, group = 12)@152 "contendedField2" Ljava.lang.Object; (contended, group = 12)@284 "contendedField3" Ljava.lang.Object; (contended, group = 15)@416 --- instance fields end ---@416 --- instance ends ---

 
 
以上是对邮件组中大牛们原始实现解释的翻译。
 
下面我们来做一个测试,看@Contended在字段级别,并且带分组的情况下,是否能解决伪缓存问题。
import sun.misc.Contended;public class VolatileLong {@Contended("group0")public volatile long value1 = 0L;  @Contended("group0")public volatile long value2 = 0L;  @Contended("group1")public volatile long value3 = 0L;  @Contended("group1")public volatile long value4 = 0L;
}

我们用2个线程来修改字段--

测试1:线程0修改value1和value2;线程1修改value3和value4;他们都在同一组中。

测试2:线程0修改value1和value3;线程1修改value2和value4;他们在不同组中。

 
测试1:
public final class FalseSharing implements Runnable {public final static long ITERATIONS = 500L * 1000L * 1000L;private static VolatileLong volatileLong;private String groupId;public FalseSharing(String groupId) {this.groupId = groupId;}public static void main(final String[] args) throws Exception {// Thread.sleep(10000);System.out.println("starting....");volatileLong = new VolatileLong();final long start = System.nanoTime();runTest();System.out.println("duration = " + (System.nanoTime() - start));}private static void runTest() throws InterruptedException {Thread t0 = new Thread(new FalseSharing("t0"));Thread t1 = new Thread(new FalseSharing("t1"));t0.start();t1.start();t0.join();t1.join();}public void run() {long i = ITERATIONS + 1;if (groupId.equals("t0")) {while (0 != --i) {volatileLong.value1 = i;volatileLong.value2 = i;}} else if (groupId.equals("t1")) {while (0 != --i) {volatileLong.value3 = i;volatileLong.value4 = i;}}}
}

 
测试2:(基于以上代码修改下面的部分)
public void run() {long i = ITERATIONS + 1;if (groupId.equals("t0")) {while (0 != --i) {volatileLong.value1 = i;volatileLong.value3 = i;}} else if (groupId.equals("t1")) {while (0 != --i) {volatileLong.value2 = i;volatileLong.value4 = i;}}}

测试1:

starting....
duration = 16821484056

测试2:

starting....
duration = 39191867777

可以看出,如果同一线程修改的是同一“contention group”中的字段,没有伪共享冲突,比有伪共享冲突的情况要快1倍多。

后记:

测试3:不使用@Contended

public class VolatileLong {public volatile long value1 = 0L;  public volatile long value2 = 0L;  public volatile long value3 = 0L;  public volatile long value4 = 0L;
}

结果:

starting....
duration = 38096777198

参考:

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/8-b132/sun/misc/Contended.java

http://openjdk.java.net/jeps/142

http://mail.openjdk.java.net/pipermail/hotspot-dev/2012-November/007309.html

转载于:https://www.cnblogs.com/Binhua-Liu/p/5623089.html

Java8的伪共享和缓存行填充--@Contended注释相关推荐

  1. 伪共享和缓存行填充,Java并发编程还能这么优化!

    前言 关于伪共享的文章已经很多了,对于多线程编程来说,特别是多线程处理列表和数组的时候,要非常注意伪共享的问题.否则不仅无法发挥多线程的优势,还可能比单线程性能还差.随着JAVA版本的更新,再各个版本 ...

  2. Java8中@Contended和伪共享 进行缓存行填充

    Java8引入了@Contented这个新的注解来减少伪共享(False Sharing)的发生.本文介绍了@Contented注解并解释了为什么False Sharing是如何影响性能的. 缓存行 ...

  3. 一篇对伪共享、缓存行填充和CPU缓存讲的很透彻的文章

    认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存的访问速度却没有质的突破,为了弥补访问内存的速度慢,充分发挥CPU的计算资源,提高CPU整体吞吐量,在CPU与内存之间引 ...

  4. 高并发之伪共享和缓存行填充(缓存行对齐)(@Contended)

    ✨ 我是喜欢分享知识.喜欢写博客的YuShiwen,与大家一起学习,共同成长!

  5. CPU Cache下的伪共享和缓存行

    本文转载自https://blog.csdn.net/karamos/article/details/80126704 认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存 ...

  6. java disruptor压测_Java并发框架Disruptor实现原理与源码分析(二) 缓存行填充与CAS操作...

    ##缓存行填充 关于缓存行填充在我个人的印象里面第一次看到是在Java的java.util.concurrent包中,因为当时很好奇其用法背后的逻辑,所以查了很多资料才明白到底是怎么回事*(也许事实上 ...

  7. java 缓存行填充_缓存伪共享问题以及解决方案缓存行填充

    缓存伪共享 共享对象存在同一个缓存中,由于MESI协议,一个对象中一些不需要改变的属性因为其他改变的属性,导致整个对象的缓存进入到M被修改状态. 目前的CPU是通常按照32或者64字节的缓存行(Cac ...

  8. java怎么缓存行填充_为什么java的Exchanger.Slot缓存行填充像这样?

    当我在 java中阅读'虚假共享'机制时,我在java.util.concurrent.Exchanger.Slot中找到以下代码 /** * A Slot is an AtomicReference ...

  9. @Contended / Disruptor 缓存行占满注解

    目录 缓存行与伪共享 Disruptor 缓存行填充 @Contended 速度测试 存储设备往往是速度越快价格越昂贵,速度越快价格越低廉.在计算机中,CPU 的速度远高于主存的速度,而主存的速度又远 ...

最新文章

  1. CSP认证201509-2 日期计算[C++题解]:枚举、模拟
  2. windows内核试验05_中断现场
  3. 笔记本win10玩红警黑屏_【买笔记本电脑差评真的有参考意义?】
  4. java web开发之 spring单元测试
  5. 在线文本按列截取工具
  6. MapReduce框架下的FP Growth算法详解
  7. Android设备通过fastboot刷入TWRP
  8. 个人风景网站模板HTML+CSS+JS(源码)
  9. 3d建模做一单多少钱?做外包赚钱吗?
  10. Linux系统安全工具之:Sxid和Skey
  11. SQL Server 2000中的并行处理和执行计划中的位图运算符
  12. 一个小时开发的直播推拉流软件来了
  13. Python - 实现渐变色的RGB计算
  14. 面向小白visual studio 2019 添加第三方库教程
  15. Web开发之电子签章
  16. 手机无线充电已不新鲜 “隔空充电”了解一下!
  17. b级车里有比迈腾空间更大的车吗?
  18. 关于Arcinfo运行AML脚本无法正确生成DHSVM河道文件的问题的解决方法
  19. Qtdesigner设计实例——计算器 +可执行文件exe制作
  20. 关注点云 专栏及博主

热门文章

  1. DNS(四)DDNS动态解析的配置
  2. APM/Pixhawk路径规划飞行(自动起飞/降落/航路点飞行)
  3. 安装ECshop2.2版本电商平台
  4. 【浪子回头遇大神】2023年5月19日:Java 之父诞生
  5. 上海财经大学金融学专业课程
  6. 《易经》64卦中的64个智慧
  7. 基于微信小程序的预约挂号系统
  8. 增量式pid+位置式PID(电机位置闭环控制)
  9. C语言求最大公约数最小公倍数
  10. 本地maven仓库配置(windows)