Disruptor以及@Contended注解
Disruptor感想
很早之前阅读过Disruptor的使用,这里有篇美团团队的文章很详细的介绍了这种队列的相关原理以及为什么这么设计和这么设计为什么处理速度很快,https://tech.meituan.com/disruptor.html,不想再“拿来主义”,本篇文章只最为记录,同时针对文章中说的相关测试,这里我会将数据插入比较大的测试贴出来;其中对这篇文章中提到但是限于篇幅没有说明的java注解contended进行说明。
CAS的队列都是无界
这个与CAS的原理有关系,它是一个不断尝试的机制,直到返回最终的正确结果或者完成操作,其源码基本上都是这样的实现:
public final int getAndAdd(int delta) {for (;;) {int current = get();int next = current + delta;if (compareAndSet(current, next))return current;}
}
如果有界应该会加锁处理边界问题。
缓存行实验
实验代码在原文中有,这里不再说明,只贴一下实验结果:
Loop times:10ms
Loop times:46ms
这里机器的配置3.2GHz,32G memory,机器配置的提升是实验数据提升的主要原因,其他实验的相关数据基本与原文中相似,这里不再赘述。
java 8 Contended注解
在Java 8中,可以采用@Contended在类级别上的注释,来进行缓存行填充。这样,可以解决多线程情况下的伪共享冲突问题。
Contended可以用于类级别的修饰,同时也可以用于字段级别的修饰,当应用于字段级别时,被注释的字段将和其他字段隔离开来,会被加载在独立的缓存行上。在字段级别上,@Contended还支持一个“contention group”属性(Class-Level不支持),同一group的字段们在内存上将是连续(64字节范围内),但和其他他字段隔离开来。
@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 ---
注意,我们使用了128 bytes的填充 – 2倍于大多数硬件缓存行的大小(cache line一般为64 bytes) – 来避免相邻扇区预取导致的伪共享冲突。
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 ---
注意contendedField1和contendedField1 和contendedField1和contendedField2和其他字段之间有填充,但是它们之间是紧挨着的,类内偏移量为4 bytes,为一个对象的大小。
下面我们来做一个测试,看@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 Volatile Long 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
下面是我的测试数据:
测试1:
starting....
duration = 26198119279
测试2:
starting....
duration = 24851524100
我的结果不知道为什么没有提升一倍,困惑!(可以看出,如果同一线程修改的是同一“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 = 23347254719
Disruptor以及@Contended注解相关推荐
- Random(二)什么是伪共享?@sun.misc.Contended注解
目录 1.背景简介 2.伪共享问题 3.问题解决 4.JDK使用示例 1.背景简介 我们知道,CPU 是不能直接访问内存的,数据都是从高速缓存中加载到寄存器的,高速缓存又有 L1,L2,L3 等层级. ...
- Java8的@sun.misc.Contended注解解决伪共享问题
本文源自转载:Java8的@sun.misc.Contended注解 目录 一.@sun.misc.Contended 介绍 二.单独使用一个缓存行有什么作用--避免伪共享 三.@sun.misc.C ...
- Java8的@sun.misc.Contended注解
@sun.misc.Contended 介绍 @sun.misc.Contended 是 Java 8 新增的一个注解,对某字段加上该注解则表示该字段会单独占用一个缓存行(Cache Line). 这 ...
- IntelliJ IDEA中使用Java8的@sun.misc.Contended注解避免伪内存共享
作用:该注解是用来消除伪内存共享的. 前提:如果要使用Contended注解,要在JVM中添加-XX:-RestrictContended参数. 遇到问题的环境:jdk12 通过如下三种方法修改JVM ...
- @Contended / Disruptor 缓存行占满注解
目录 缓存行与伪共享 Disruptor 缓存行填充 @Contended 速度测试 存储设备往往是速度越快价格越昂贵,速度越快价格越低廉.在计算机中,CPU 的速度远高于主存的速度,而主存的速度又远 ...
- java jdk注解 使用_JDK中注解@Contended是干什么的?怎么使用?
避免伪共享(false sharing). 先引用个伪共享的解释: 缓存系统中是以缓存行(cache line)为单位存储的.缓存行是2的整数幂个连续字节,一般为32-256个字节.最常见的缓存行大小 ...
- 太牛逼了!项目中用了Disruptor之后,性能提升了2.5倍
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 存储设备往往是速度越快价格越昂贵,速度越快价格越低廉.在 ...
- java项目中用了Disruptor之后,性能提升了10倍
缓存行与伪共享 缓存中的数据并不是独立的进行存储的,它的最小存储单位是缓存行,缓存行的大小是2的整数幂个字节,最常见的缓存行大小是 64 字节.CPU 为了执行的高效,会在读取某个对象时,从内存上加载 ...
- 【java并发编程】无锁并发框架disruptor
一.简介 Disruptor是一个高性能队列,研发的初衷是解决内部的内存队列的延迟问题,而不是分布式队列.基于Disruptor开发的系统单线程能支撑每秒600万订单. 使用场景:对延时要求很高的场景 ...
最新文章
- Oracle大佬离职,怒喷MySQL是“糟糕的数据库”……
- 前端一HTML:十七:背景
- QT的QSplashScreen类的使用
- PHP 如何准确取得服务器地址IP[非代理]
- Ubuntu18.04换源更新国内源
- 使用Maven安装本地jar
- php无法用mail函数发送邮件之原因
- aix查看oracle数据库端口号,通过netstat+rmsock查找AIX端口对应进程
- RHEL 8 - 用OpenSCAP工具对容器镜像进行漏洞安全合规扫描,并修复
- 确认oracle是否繁忙,Oracle系统繁忙时,快速定位
- 风控建模九:一些特征工程方法及自动化工具小结
- 怎么给照片加眼镜(二):3D眼镜模型合成法
- 使用TypeScript重构Axios:让你的项目更加完善
- powereshell判断目录如果存在pdf文件则打包文件发送到指定邮箱
- 如何在scrapy框架里进行调试嘞
- 【kafka】解决kafka-tool连接上kafka,brokers和topics不显示问题
- 模拟量的输入和输出问题及选型
- 推荐 16 个优秀的 Vue 开源项目
- 解决office for mac 的word显示字体乱码问题
- 【转自Oracle官方博客】一个ASMCA无法识别磁盘设备的问题