范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Disruptor为什么会如此快(三)揭秘内存屏障(volatile)

  前言
  本文讨论什么是内存屏障(其实它是软件协调硬件工作的一个指令),以及如何应用volatile(在JAVA世界中内存屏障的语义是由volatile关键字来实现的),最后介绍在disruptor中如何应用的。
  什么是内存屏障
  它是一个CPU指令。我们在讨论CPU级别的东西,以便获得我们想要的性能(Martin著名的Mechanical Sympathy理论https://mechanical-sympathy.blogspot.com/2011/07/memory-barriersfences.html)。
  基本上,它是这样一条指令: a)确保一些特定操作执行的顺序; b)影响一些数据的可见性(可能是某些指令执行后的结果)。
  编译器和CPU可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。
  内存屏障另一个作用是强制更新一次不同CPU的缓存。例如,一个写屏障会把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将得到最新值,而不用考虑到底是被哪个cpu核心或者哪颗CPU执行的。
  和JAVA什么关系
  JAVA里有个关键字叫volatile, 在JAVA世界中内存屏障的语义是由volatile关键字来实现的  。JAVA 语言是支持多线程的,为了解决多线程的安全问题 JAVA 语言引进了 synchronized 同步块和 volatile关键字机制。
  volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的"可见性"。可见性的意思是当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
  为什么要使用volatile
  解决多线程下数据安全共享的问题;volatile变量修饰符如果使用恰当的话,它比synchronized的使用和 执行成本会更低 ,因为它不会引起线程上下文的切换和调度。
  volatile的实现原理
  那么Volatile是如何来保证可见性的呢?在x86处理器下通过工具获取JIT编译器生成的汇编指令来看看对Volatile进行写操作CPU会做什么事情。
  Java代码:
  instance = new Singleton();//instance是volatile变量
  汇编代码:
  0x01a3de1d: movb $0x0,0x1104800(%esi);0x01a3de24: lock addl $0x0,(%esp);
  有volatile变量修饰的共享变量进行写操作的时候会多第二行汇编代码,通过查IA-32架构软件开发者手册可知,lock前缀的指令在多核处理器下会引发了两件事情。  将当前处理器缓存行的数据会写回到系统内存。  这个写回内存的操作会引起在其他CPU里缓存了该内存地址的数据无效。
  处理器为了提高处理速度,不直接和内存进行通讯,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完之后不知道何时会写到内存,如果对声明了Volatile变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题,所以在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器要对这个数据进行修改操作的时候,会强制重新从系统内存里把数据读到处理器缓存里。
  小贴士:
  这两件事情在IA-32软件开发者架构手册的第三册的多处理器管理章节(第八章)中有详细阐述。
  Lock前缀指令会引起处理器缓存回写到内存 。Lock前缀指令导致在执行指令期间,声言处理器的 LOCK# 信号。在多处理器环境中,LOCK# 信号确保在声言该信号期间,处理器可以独占使用任何共享内存。(因为它会锁住总线,导致其他CPU不能访问总线,不能访问总线就意味着不能访问系统内存),但是在最近的处理器里,LOCK#信号一般不锁总线,而是锁缓存,毕竟锁总线开销比较大。在8.1.4章节有详细说明锁定操作对处理器缓存的影响,对于Intel486和Pentium处理器,在锁操作时,总是在总线上声言LOCK#信号。
  但在P6和最近的处理器中,如果访问的内存区域已经缓存在处理器内部,则不会声言LOCK#信号。相反地,它会锁定这块内存区域的缓存并回写到内存,并使用缓存一致性机制来确保修改的原子性,此操作被称为"缓存锁定",  缓存一致性机制会阻止同时修改被两个以上处理器缓存的内存区域数据。
  一个处理器的缓存回写到内存会导致其他处理器的缓存无效 。 IA-32处理器和Intel 64处理器使用MESI(修改,独占,共享,无效)控制协议去维护内部缓存和其他处理器缓存的一致性。在多核处理器系统中进行操作的时候,IA-32 和Intel 64处理器能嗅探其他处理器访问系统内存和它们的内部缓存。它们使用嗅探技术保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。例如在Pentium和P6 family处理器中,如果通过嗅探一个处理器来检测其他处理器打算写内存地址,而这个地址当前处理共享状态,那么正在嗅探的处理器将无效它的缓存行,在下次访问相同内存地址时,强制执行缓存行填充。
  volatile使用误区
  volatile很容易被误用,用来进行原子性操作。
  与使用 synchronized相比,声明一个 volatile 字段的区别在于没有涉及到锁操作。但特别的是对 volatile 字段进行" ++ "这样的读写操作不会被当做原子操作执行。同时测试了字符串拼接的操作,也是不安全的。下文中有代码已经证实 volatile 关键字修饰的变量在多线程写的情况下不安全。
  public class Counter { //    public volatile static int count = 0;       public volatilestatic StringBuilder count = new StringBuilder("aa");       public static void inc(String a){         //这里延迟1毫秒,使得结果明显         try {             Thread.sleep(1);         } catch (InterruptedExceptione) {           }             count .append(a+"-");       }       public static void main(String[]args) {         //同时启动1000个线程,去进行i++计算,看看实际结果         for (int i = 1; i <= 1000; i++) {             int finalI = i;             new Thread(new Runnable(){                 @Override                 publicvoid run() {                    Counter.inc(String.valueOf(finalI));                 }             }).start();         }         //这里每次运行的值都有可能不同,可能为1000         System.out.println("运行结果:Counter.count=" + Counter.count);     } }
  运行结果还是没有我们期望的 aa-XX-1000,下面我们分析一下原因。
  在 java 垃圾回收整理一文中,描述了 jvm 运行时刻内存的分配。其中有一个内存区域是 jvm 虚拟机栈,每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存变量的具体值 load 到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,变量的具体值 load 到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。下面一幅图描述这些交互。
  read and load 从主存复制变量到当前工作内存
  use and assign 执行代码,改变共享变量值
  store and write 用工作内存数据刷新主存相关内容, 其中 use and assign 可以多次出现,但是这一些操作并不是原子性,也就是在 read load 之后,如果主内存 count 变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,所以计算出来的结果会和预期不一样。
  对于 volatile 修饰的变量, jvm 虚拟机只是保证从主内存加载到线程工作内存的值是最新的。
  例如假如线程 1 ,线程 2 在进行 read,load 操作中,发现主内存中 count 的值都是 5 ,那么都会加载这个最新的值
  在线程 1 堆 count 进行修改之后,会 write 到主内存中,主内存中的 count 变量就会变为 6
  线程 2 由于已经进行 read,load 操作,在进行运算之后,也会更新主内存 count的变量值为 6
  导致两个线程及时用 volatile 关键字修改之后,还是会存在并发的情况。
  暂且不管在多线程下对 volatile 修饰的变量进行修改是否安全的,但是在多线程下对 volatile 修饰的变量进行读操作是安全的,后一点是无可非议的。所以有了以下的结论:   volatile 的适用场景当只有一个线程可以修改字段的值,其它线程可以随时读取,那么把字段声明为 volatile 是合理的。
  对性能的影响
  内存屏障作为另一个CPU级的指令,没有锁那样大的开销,可以看之前文章
  Disruptor为什么会如此快 - (一)锁的成本的实验结果。内核并没有在多个线程间干涉和调度。
  Disruptor如何使用内存屏障
  下图是Disruptor的AbsttactSequencer源码截图。
  把Sequence定义为volatile 类型,供多线程共享。从截图可以看出gatingSequences在对象实例化时进行了初始化(写操作),多处进行读操作。这个与先前得出的结论颇为相似:  volatile 的适用场景当只有一个线程可以修改字段的值,其它线程可以随时读取,那么把字段声明为 volatile 是合理的。
  小结
  1、内存屏障是CPU指令,它允许你对数据什么时候对其他进程可见作出假设。在Java世界里,使用volatile关键字来实现内存屏障。使用volatile意味着不用被迫选择加锁,并且还能获得性能的提升。
  2、volatile的适用场景:当只有一个线程可以修改字段的值,其它线程可以随时读取,那么把字段声明为 volatile 是合理的。
  如果此文能帮小伙伴答疑解惑,请  关注「架构那些事儿」
  你的关注就是我的动力!

足球转会信息8月11日1北京时间8月11日,上海海港俱乐部官方宣布,武磊结束留洋生涯,回归加盟球队。上海海港也在官方公告中使用了如7归来这一词语,意味着武磊此次回归,将再次穿回自己标志性的红色7号球衣。转会11曼联关窗前有望再签三球星英超升班马有意签蓝军旧将曼联未来几周或再签三人曼联在上周末主场12负于布莱顿,拉开了202223赛季英超联赛的序幕。新签约的马拉恰利桑德罗马丁内斯和埃里克森都在本赛季的首场比赛中亮相,但这对埃里克滕哈格担月升沧海程始是怎样的存在,为何连上朝的资格都没有,他不配?由赵露思和吴磊等人主演的电视剧星汉灿烂,月升沧海正在热播中,该剧主要讲述程家幺女程少商与少年将军凌不疑之间的爱情故事。相信大家都有一个疑问,为何曲凌候程始夫妇连上朝的资格都没有,他利物浦续约小将!5000万绯闻中场辟谣近日,有关我军是否再会进行中场引援的话题,每天都会有不少媒体进行报道,甚至有葡萄牙媒体表示,我军将会签下效力于葡萄牙体育的马特乌斯努内斯。葡萄牙媒体CorreioDaManha今天从13,省运会U15是否假球?如今也有结论,球迷没想到广东省省运会U15(男子乙A组)决赛中,广东队在13落后的情况下,完成了对清远队53的大逆转。然而在赛后却有人说这是一场假球,那么到底是不是假球呢?如今也有了结论,球迷纷纷表示没想官网庆祝!中国队拿下第5场比赛胜利,水庆霞谈成功秘诀北京时间8月11日消息,在美国拉练的中国女足以40完胜旧金山大学女足,自此,中国女足在美国热身赛中拿到3胜1平1负的佳绩。赛后,中国女足官网第一时间发文庆祝中国女足海外拉练第五场热盘点NBA不堪一击的五大天选之子,艾顿生不逢时,奥登昙花一现天才免不了有障碍,因为障碍会创造天才。这句罗曼罗兰的名言同样适用于NBA的舞台。在星光璀璨的NBA舞台,出道即被寄予厚望,贵为天选之子的超级球星屡见不鲜。但,能够兑现天赋,破茧成蝶丑羞耻懒傲15假球案论中国足协的五行现在是2022年8月11日下午1755分,再有5分钟,中国足协的工作人员想必就该下班了吧,可是苦苦等待广东U15假球案结果的广大球迷们已经这是已经等待了第四天,四天来毫无口风毫无通莫吉尤文不应让博格巴做出选择,该做手术的话就应该接受直播吧8月11日讯尤文中场博格巴此前遭遇半月板伤病,但为了不影响自己参加世界杯,法国人选择了保守治疗而不是接受手术。对于博格巴的做法,前尤文总经理莫吉发表了自己的看法。在接受Bia卡佩罗每个人都在讨论迪巴拉但罗马真正的大手笔是签下了马蒂奇意大利媒体romapress报道,意甲名帅卡佩罗点评了自己曾经执教的罗马。卡佩罗认为,每个人都在讨论迪巴拉,但是罗马最好的大手笔是签下了马蒂奇,他是真正的交易。值得一提的是,马蒂奇亚洲杯八强赛韩国男排3比2力挫日本男排京报体育记者孔宁在2022年8月11日晚,在泰国佛统举行的2022年男排亚洲杯八强复赛E组比赛中,上演了日韩男排强强对话。日本男排这一次参赛的是二队,韩国男排则是一线队。日韩两强对
冬奥开幕在即公园冰雪项目人气火爆村民儿时玩的冰车又火了新京报讯(记者曹晶瑞)2022年北京冬奥会开幕在即,3日,新京报记者走访发现,北海公园北京城市绿心森林公园等公园内的冰上项目冰雪嘉年华活动吸引了不少市民前来打卡,滑冰冰车冰上自行车倒计时7天!金球奖官方疯狂暗示获奖者来自法甲,梅老七成真今天距离2021金球奖颁奖盛典(11月29日)只有7天的时间了,按照惯例,获奖者将在颁奖的一周前得到消息。也就是说,现在法国足球官方已经将结果告知获奖者。事实上,这段时间,包括葡萄网络中国节春节平乡县春节联欢晚会丰富群众文娱生活春节期间,平乡县文化广电体育和旅游局主办的平乡县红心向党大爱平乡春节联欢晚会在线上与观众见面。整场晚会以魅力中国党的光辉非遗明珠平乡风采一起向未来五个主题进行,包括歌颂中国共产党安范志毅社媒晒全家福,第三任妻子靓丽出镜,女儿浓妆艳抹甜美动人北京时间2月4日,一年一度的春节是全中国人民阖家团聚的日子,就算是一些明星也不例外,他们也希望在忙碌的一年过后能够有短暂的时间见一见自己的家人。而已经退役后的范志毅同样如此,尽管他单眼皮也很帅的男星,我只服这5位!个个气质内敛,很有魅力眼睛的眼皮其实对眼睛的风格有很大的影响。双眼皮给人的感觉更加外放更有亲和力,而单眼皮则更容易产生一种淡淡的神采。今天我们分享的5位男星就是好看的单眼皮类型,虽然没有双眼皮的剑眉星目内娱穿西装最好看的十位男明星肖战气质绝佳,任嘉伦一人千面娱乐圈男明星来来回回就那么几个惊艳的,主要是没有黑料的,那真的是少之又少。不过也不是说没有,接下来这几位都是内娱颜霸级别的,盘点一下这些人的西装造型,真的是颜值暴击。看看有没有你们李连杰两次婚姻4个女儿,有的被捧在手心,有的变成留守儿童01hr这世上有一种悲哀,叫同爹不同命!2007年,在刘德华的演唱会上,一个7岁的小女孩和天王大跳舞蹈,出尽了风头。刘德华宠溺的眼神,简直羡煞旁人。2008年,李连杰牵着身穿绿纱裙谁还没有一条小白裤?清爽减龄又利落大方,早春穿着更时髦春节过后,天气也越来越暖和了,早春的穿搭应该及时安排起来了。很多姑娘在春天里喜欢穿碎花的裙子,浪漫而又温柔。但相对于裙装的唯美,我还是更喜欢裤装的洒脱。尤其是春天里的小白裤,更是利早春裤子穿搭合辑,真的是太美了,让你变得好看又有气质立春过后,天气也会越来越暖了。时髦而又洋气的穿搭,成为女性最喜欢的风格。春天里万物复苏,大地回暖,我们也迫不及待地脱掉厚重的棉衣,换上轻薄的服装,享受着温暖的阳光。当然也有很多姑娘3消息!格里芬嘲笑斯图尔特,克莱下月复出,阿泰和KD母亲同游关于格里这段时间,NBA最大的新闻无疑是詹姆斯跟活塞球员斯图尔特的冲突,事情是怎么发生的,想必很多球迷都知道了,这里我就不多BB了。赛后关于这次冲突,还有很多人发声,冲突中比较冲动12连胜,联盟第二!也许我们都高估了,保罗和菲尼克斯太阳菲尼克斯太阳在击败掘金之后,成功拿到了12连胜,队史第四长的连胜纪录,所有人都在感慨,我们低估了这支太阳队,他们依旧是联盟顶级的球队,处于夺冠热门行列,但事实上,真的如此吗?12连