在我的前一篇文章<伪共享和缓存行填充,从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:

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

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

TestContended$ContendedTest2: field layout
Entire 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 {
@Contended
private 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 {
@Contended
private Object contendedField1; @Contended
private 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 ---
注意$contendedField1 和$contendedField2和其他字段之间有填充,但是它们之间是紧挨着的。
 
 
以上是对邮件组中大牛们原始实现解释的翻译。
 
下面我们来做一个测试,看@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

Java8的伪共享和缓存行填充--@Contended注释的更多相关文章

  1. 伪共享和缓存行填充,从Java 6, Java 7 到Java 8

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

  2. 剖析Disruptor:为什么会这么快?(二)神奇的缓存行填充

    原文链接:http://mechanitis.blogspot.com/2011/07/dissecting-disruptor-why-its-so-fast_22.html 需FQ 计算机入门   ...

  3. 简述伪共享和缓存一致性MESI

    什么是伪共享 计算机系统中为了解决主内存与CPU运行速度的差距,在CPU与主内存之间添加了一级或者多级高速缓冲存储器(Cache),这个Cache一般是集成到CPU内部的,所以也叫 CPU Cache ...

  4. java中伪共享问题

    伪共享(False Sharing) 原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson  译者:丁一 缓存系统中是以缓存行(cache l ...

  5. 多线程中的volatile和伪共享

      伪共享 false sharing,顾名思义,“伪共享”就是“其实不是共享”.那什么是“共享”?多CPU同时访问同一块内存区域就是“共享”,就会产生冲突,需要控制协议来协调访问.会引起“共享”的最 ...

  6. 伪共享(False Sharing)

    原文地址:http://ifeve.com/false-sharing/ 作者:Martin Thompson  译者:丁一 缓存系统中是以缓存行(cache line)为单位存储的.缓存行是2的整数 ...

  7. 从缓存行出发理解volatile变量、伪共享False sharing、disruptor

    volatilekeyword 当变量被某个线程A改动值之后.其他线程比方B若读取此变量的话,立马能够看到原来线程A改动后的值 注:普通变量与volatile变量的差别是volatile的特殊规则保证 ...

  8. CPU Cache与缓存行

    编译环境:windows10+Idea+x86 CPU. 1.CPU Cache CPU 访问内存时,首先查询 cache 是否已缓存该数据.如果有,则返回数据,无需访问内存:如果不存在,则需把数据从 ...

  9. 伪共享(false sharing),并发编程无声的性能杀手

    在并发编程过程中,我们大部分的焦点都放在如何控制共享变量的访问控制上(代码层面),但是很少人会关注系统硬件及 JVM 底层相关的影响因素.前段时间学习了一个牛X的高性能异步处理框架 Disruptor ...

随机推荐

  1. 【译】SQL Server索引进阶第八篇:唯一索引

    原文:[译]SQL Server索引进阶第八篇:唯一索引     索引设计是数据库设计中比较重要的一个环节,对数据库的性能其中至关重要的作用,但是索引的设计却又不是那么容易的事情,性能也不是那么轻易就 ...

  2. magento后台paypal设置

    如何在magento后台设置paypal呢? 这边把整理的简单跟大家分享一下. 1.system->config-paypel1.1 Merchant Country 设置国家1.2 Email ...

  3. 终端启动apache,mysql服务;登录mysql服务器

    sudo apachectl start sudo mysql.server start sudo apachectl help 查看帮助 mysql -hlocalhost -uroot -p ma ...

  4. javascript数据结构和算法

    一.栈 javascript实现栈的数据结构(借助javascript数组原生的方法即可) //使用javascript来实现栈的数据结构 var Stack={ //不需要外界传参进行初始化,完全可 ...

  5. 跟着思维导图学习javascript

    1.javascript 变量 2.javascript 运算符 3.javascript 数组 4.javascript 流程语句 5.javascript字符串函数 6.javascript 函数 ...

  6. PHP服务器配置环境变量

    我们写的PHP应用程序,通常会分别在本地.开发.测试.RC.生产环境中运行,不同环境中全局变量各不相同.通常简单的部署做法是,每次部署到一个环境,都需要先修改对应的全局变量,然后再部署代码.如果部署频 ...

  7. python入门到精通[三]:基础学习(2)

    摘要:Python基础学习:列表.元组.字典.函数.序列化.正则.模块. 上一节学习了字符串.流程控制.文件及目录操作,这节介绍下列表.元组.字典.函数.序列化.正则.模块. 1.列表 python中 ...

  8. HTML5音频视频-视频播放

  9. Linux操作系统备份之三:通过二进制拷贝(dd)方式实现Linux操作系统数据的备份

    前面有两篇文章,<Linux操作系统备份之一:使用LVM快照实现Linux操作系统数据的在线备份>和<Linux操作系统备份之二:通过tar拷贝分区实现Linux操作数据的在线备份& ...

  10. chartControl绑定数据源

    private void BindChartControl(DataTable dataTable,ChartControl chartControl,int[] columnNO,ViewType ...