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

Java内存模型(JMM)

  在高并发的情况下,java的内存模型到底是怎么提供支持的,要说清楚这个问题我们首先要先知道一些硬件层面的的知识,因为java的内存模型都是架构在这些硬件层面上的。计算机的存储结构
  存储器的层次结构
  计算机存储结构为金字塔型存储
  这种结构主要由寄存器,缓存(cache),内存,硬盘,远程文件存储等这几部分组成。有如下两个特点:越接近金字塔顶端速度越快,容量越小,价格越贵 每一种存储器设备只和它相邻的存储设备打交道
  需要注意的是L3高速缓存是主板上被所有CPU所共享的。
  之所以是金字塔型的结构主要是局部性原理。
  程序局部性原理时间局部性
  刚被访问过的存储单元很可能不久又被访问,通常体现在循环执行的指令。让最近被访问过的信息保留在靠近CPU的存储器中加快处理速度。空间局部性
  刚被访问过的存储单元的邻近单元很有可能不久会被访问,通常体现在顺序执行的指令。将刚被访问的存储单元的邻近单元调到靠近CPU的存储器加快访问。硬件层数据一致性
  硬件层数据一致性
  我们思考这样一个问题,当CPU需要主存中读取一个X数值时,最终会被CPU1和CPU2load到自己的核心中,如果CPU1修改了X的值,设置成了1,CPU2修改了X的值设置成了2,那么就产生了数据的不一致性,那么不同核中的数据要怎么保持一致性呢?也就是说我们运用了金字塔的存储模型,速度会提高。但是会有数据不一致性的问题。要解决这个问题就需要在硬件层面进行解决。方法如下:
  总线锁
  锁住总线
  当CPU1通过总线bus访问x的值时,CPU2是不允许再访问x。因为锁住的是总线,除了访问x,访问其他的数值也是不允许的。
  总线锁会锁住总线,使得其他CPU甚至不能访问内存中其他的地址,当时此种方式效率较低。
  再老的一些CPU上会采用总线锁的方式。新的CPU采用各种各样的一致性协议。
  一致性协议
  缓存一致性协议
  这里的一致性协议会有很多, 包括MSI、MESI、MOSI Synapse、Firefly及Draqon,intel采用的是MESI,我们这里重点介绍这种协议。
  MESI(也称伊利诺斯协议)是一种广泛使用的支持写回策略的缓存一致性协议,该协议被应用在Intel奔腾系列的CPU中。
  CPU中每个缓存行使用的4种状态进行标记(使用额外的两位bit表示)
  状态
  描述
  M(Modified)修改
  这行数据有效,数据被修改了,和内存中的数据不一样,数据只存在于本cache中。
  E(Exclusive)独享/互斥
  这行数据有效,数据和内存中的数据一致,数据只存下于本Cache中
  S(Shared)共享
  这行数据有效,数据和内存中的数据一致,数据存在于很多cache中
  I(Invalid)无效
  这行数据无效
  E状态
  E状态
  只有Core 0访问变量x,它的Cache line状态为E(Exclusive)。
  S状态
  S状态
  3个Core都访问变量x,它们对应的Cache line为S(Shared)状态。
  M状态和状态之间的转化
  M状态和状态之间的转化
  Core 0修改了x的值之后,这个Cache line变成了M(Modified)状态,其他Core对应的Cache line变成了I(Invalid)状态。
  在MESI协议中,每个Cache的Cache控制器不仅知道自己的读写操作,而且也监听其它Cache的读写操作。每个Cache line所处的状态根据本核和其它核的读写操作在4个状态间进行迁移。
  需要注意的是MESI并没有完全解决锁总线的问题,我们说MESI是缓存锁,范围比起总线锁会小很多。但是有一些无法被缓存的数据或者跨越多个缓存行的数据还是需要总线锁。
  现代CPU底层的数据一致性实现是采用总线锁加缓存锁来实现的。
  缓存行
  当CPU访问某个数据时,会假设该数据附近的数据以后会被访问到,因此,第一次访问这一块区域时,会将该数据连同附近区域的数据(共64字节)一起读取进缓存中,那么 这一块数据称为一个Cache Line 缓存行 。 在一般的x86环境下一个 CacheLine  是 64  字节。
  伪共享
  缓存系统中是以缓存行(cache line)为单位存储的,当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
  伪共享
  例如,CPU 1在读取数据X时,会把相邻的Y也会加载到其缓存行中,此时如果CPU2 需要读取数值Y,也会把相邻的X也加载缓存行中,此时如果CPU1修改数值X时根据缓存一致性协议会导致CPU2中的缓存行失效进而重读,如果CPU2修改数值Y时也会导致CPU1中的缓存行失效重读,这样就导致了CPU间彼此影响导致性能损耗。
  一般我们通过缓存行对齐就可以解决这样的问题。
  缓存行对齐
  基于以上问题的分析,在一些情况下,比如会频繁进行操作的数据,可以根据缓存行的特性进行缓存行对齐(即将要操作的数据凑一个缓存行进行操作)下面使用一个示例进行说明:  package com.example.demo;   public class Cacheline_nopadding {     public static class T{         //8字节         private volatile long x = 0L;     }     private static T[] arr = new T[2];       static {         arr[0] = new T();         arr[1] = new T();     }       public static void main(String[] args) throws InterruptedException {         Thread thread1 = new Thread(()->{             for(long i = 0;i < 1000_0000L;i++){                 //volatile的缓存一致性协议MESI或者锁总线,会消耗时间                 arr[0].x = i;             }         });           Thread thread2 = new Thread(()->{             for(long i = 0;i< 1000_0000L;i++){                 arr[1].x = i;             }         });         long startTime = System.nanoTime();         thread1.start();         thread2.start();         thread1.join();         thread2.join();         System.out.println("总计消耗时间:"+(System.nanoTime()-startTime)/100_000);     } }
  总计消耗时间:2508
  下面来做一个改造升级,对齐缓存行,重点代码如下 private static class Padding{         //7*8字节         public volatile long p1,p2,p3,p4,p5,p6,p7;     }     public static class T extends Padding{         //8字节         private volatile long x = 0L;     }
  通过上述代码做缓存对齐,每次都会有初始的7*8个占位,加上最后一个就是独立的一块缓存行,整理后代码如下: package com.example.demo;   public class Cacheline_padding {     private static class Padding{         //7*8字节         public volatile long p1,p2,p3,p4,p5,p6,p7;     }     public static class T extends Padding{         //8字节         private volatile long x = 0L;     }     private static T[] arr = new T[2];       static {         arr[0] = new T();         arr[1] = new T();     }       public static void main(String[] args) throws InterruptedException {         Thread thread1 = new Thread(()->{             for(long i = 0;i < 1000_0000L;i++){                 //volatile的缓存一致性协议MESI或者锁总线,会消耗时间                 arr[0].x = i;             }         });           Thread thread2 = new Thread(()->{             for(long i = 0;i< 1000_0000L;i++){                 arr[1].x = i;             }         });         long startTime = System.nanoTime();         thread1.start();         thread2.start();         thread1.join();         thread2.join();         System.out.println("总计消耗时间:"+(System.nanoTime()-startTime)/100_000);     } }
  总计消耗时间:729
  从上面可以看到,使用缓存对齐,相同操作情况下对齐后的时间比没对齐的时间减少一半。
  上面这种缓存行填充的方法在早期是比较流行的一种解决办法,比较有名的Disruptor框架就采用了这种解决办法提高性能,Disruptor是一个线程内通信框架,用于线程里共享数据。与LinkedBlockingQueue类似,提供了一个高速的生产者消费者模型,广泛用于批量IO读写,在硬盘读写相关的程序中应用十分广泛,Apache旗下的HBase、Hive、Storm等框架都有使用Disruptor。硬件级别保证有序
  乱序问题
  CPU为了提高指令的执行效率,比如在执行一条读指令(读取内存的一条数据)时,去执行另一条指令,前提是两条指令间没有依赖关系。
  我们来看这样的例子public class Disorder {     private static int x = 0, y = 0;     private static int a = 0, b =0;      public static void main(String[] args) throws InterruptedException {         int i = 0;         for(;;) {             i++;             x = 0; y = 0;             a = 0; b = 0;             Thread one = new Thread(new Runnable() {                 public void run() {                     a = 1;                     x = b;                 }             });              Thread other = new Thread(new Runnable() {                 public void run() {                     b = 1;                     y = a;                 }             });             one.start();other.start();             one.join();other.join();             String result = "第" + i + "次 (" + x + "," + y + ")";             if(x == 0 && y == 0) {                 System.err.println(result);                 break;             } else {                 //System.out.println(result);             }         }     }       public static void shortWait(long interval){         long start = System.nanoTime();         long end;         do{             end = System.nanoTime();         }while(start + interval >= end);     } }
  在线程体中未发生乱序问题,那么程序输出的结果就不应该有x=0,y=0的结果,但是根据实际执行情况来看,会输出以下内容
  第3932682次 (0,0)
  也就是说,是存在乱序问题的。
  硬件内存屏障 X86sfence:在sfence指令前的写操作当必须在sfence指令后的写操作前完成lfence:在lfence指令前的读操作当必须在lfence指令后的读操作前完成mfence:在mfence指令前的读写操作当必须在mfence指令后的读写操作前完成
  原子指令
  如x86上的"lock …" 指令是一个Full Barrier,执行时会锁住内存子系统来确保执行顺序,甚至跨多个CPU。Software Locks通常使用了内存屏障或原子指令来实现变量可见性和保持程序顺序。JVM级别保证有序性
  JVM级别内存屏障
  这些都是JVM级别定义的一些规范,具体的实现都是由硬件级别的内存屏障进行保障与支持的。volatile
  volatile用来修饰成员变量(静态变量和实例变量),被修饰的变量在被修改时能够保证每个线程获取该变量的最新值,从而避免出现数据脏读的现象,也就是我们说的保证数据的可见性。volatile可以保证可见性和有序性,但是不能保证原子性。要保证原子需要synchronized和Lock。
  字节码级别
  字节码级别变化
  被volatile修饰的变量在字节码级别的access flags中会多一个volatile字符串
  JVM级别
  JVM级别
  在JVM级别就是根据volatile的写或读操作添加了相应的内存屏障。
  StoreStore屏障:禁止上面的普通写和下面的volatile写重排序;
  StoreLoad屏障:防止上面的volatile写与下面可能有的volatile读/写重排序
  LoadLoad屏障:禁止下面所有的普通读操作和上面的volatile读重排序
  LoadStore屏障:禁止下面所有的普通写操作和上面的volatile读重排序
  硬件层面
  通过相应工具查看汇编指令可以发现,在window上是通过lock指令来实现的。
  因此,volatile修饰的变量具有以下的特点:Lock前缀的指令会引起处理器缓存写回内存;一个处理器的缓存回写到内存会导致其他处理器的缓存失效;当处理器发现本地缓存失效后,就会从内存中重读该变量数据,即可以获取当前最新值。

二十大代表热议丨把基层党组织建设成为坚强战斗堡垒二十大报告提出,增强党组织政治功能和组织功能,坚持大抓基层的鲜明导向,把基层党组织建设成为有效实现党的领导的坚强战斗堡垒,激励党员发挥先锋模范作用,保持党员队伍先进性和纯洁性。基层倪萍被父亲抛弃,被陈凯歌劈腿,拿走赵忠祥5亿遗产还记得以前在过年时,我们经常在电视上看到的那个春晚女神倪萍吗?她一共主持过13届春晚,是当之无愧的央视一姐。别看她在事业上如鱼得水,风光背后却是无尽心酸。01。亲情父亲不管不顾,从火箭式升温,广东又慢慢热红了!这几招帮你缓解干燥最近天气凉爽让大家体会了一把秋天的感觉外套也终于有了用武之地不少网友表示广东这几天的天气可太舒服了那么问题来了这样舒适的天气还会持续多久?近日一个词条冲上同城热搜广东又慢慢热红了没CBA积分榜大乱!浙江领跑辽宁下滑一位,同曦第九广东倒数第一目前CBA常规赛第一阶段的比赛正在进行当中,可以说比赛的激烈程度是前所未有的,由于本赛季CBA向国际篮联判罚规则接轨,也使得比赛异常的激烈,我们的CBA联赛是越来越好看了,但同时也迪拜最美公主梅萨国王情人所生,当运动员为父亲争光,42岁未婚2008年,中国举行了盛大的奥运会开幕式。各国奥运会代表团都呈现出良好的精神风范,走向了奥运会的会场,他们大多身着运动服。在人群里面还有一只来自迪拜的队伍,他们身穿素净的黑袍,缓缓一切皆有可能球是圆的,一切皆有可能。这句话很入味,很值得品味其中的圆滑。而,人是扁的,更是一切皆有可能的缔造者。换句话说,也就是人比球更圆滑。于是,能够考虑他人感受的圆滑,叫憨厚不考虑他人感受年青时的梦你还记得吗?也许它的名字叫理想一天与一位相识多年的朋友相见,久别重逢格外高兴,就找了个地方吃饭聊天。席间随意的聊了一些话题,什么家常里短,工作问题,生活看法,疫情问题等。聊了一会我突然间问了一句,还记得我们以前JDG晋级4强,TES官博沦陷!粉丝吐槽RGE这么菜,TES怎么输的?JDG在S12世界赛8强赛中以30的战绩淘汰RGE,晋级4强。按理说这是让人开心的事情,但是没想到却有战队因此被骂得更惨了,这支战队就是在小组赛首轮中败给RGE的TES。在JDG晋结婚时穿了件粉色衣服,被陌生人说是二婚?网友回复绝了哈哈哈哈今天的各位宝子们有没有一如既往保持开心呢今日份快乐已送达快来查收咯01hr结婚应该穿什么看到新娘穿粉色衣服有些网友坐不住了02hr千万不要跟蜜蜂玩否则03hr第一次当护士活菩萨见过人际交往的目的,就是愉悦别人和愉悦自己头号解忧馆今天是早起自律打卡第七天坚持早起运动和读书学习,让我越来越不骄不躁,忙而不盲。我也终于学会和自己自洽,不再因为人际关系内耗自己。我相信,这个世界上一定有真正美好的人我也相乌土领导人进行第六次会面!两国关系达新高度?10月21日,土库曼斯坦首都阿什哈巴德奥古兹汗宫举行乌兹别克斯坦总统米尔济约耶夫的正式会晤仪式。之后,两国总统举行小范围会谈。会议指出,近年来,两国在各个实际合作领域取得突破性进展