zoukankan      html  css  js  c++  java
  • java高并发核心要点|系列5|CPU内存伪共享

    上节提到的:伪共享,今天我们来说说。

    那什么是伪共享呢?

    这得从CPU的缓存结构说起。以下如图,CPU一般来说是有三级缓存,1 级,2级,3级,越上面的,越靠近CPU的,速度越快,成本也越高。也就是说速度方面:1级>2级>3级。

    图1

     图2

    如上图2我们来看看不同级别的缓存的时延:

       

    到CPU的延迟CPU时钟耗时
    主内存 很多(Multiple) ~60-80 ns
    L3 缓存 ~40-45 周期 ~15 ns
    L2 缓存 ~10 周期 ~3 ns
    L1 缓存 ~3-4 周期 ~1 ns
    寄存器 一周期          小于1ns,飞快

     更多CPU架构信息:https://blog.csdn.net/karamos/article/details/80126704

    说到这里,我们要理解一个很重要的概念:缓存行。什么是缓存行?

    首先我们来看这几级缓存,其中,1,2级缓存是CPU核心私有的,也就是说每个核,之间不会共享1,2级缓存,那它们之间怎么通信或共享数据呢?

    答案是:3级缓存,如下图:

     那core1,和core2之间,是通过什么方式共享缓存呢?

    答案是:缓存行!

    什么是缓存行?简单来说,就是CPU内核之间共享数据的最小单位。如下图:x,y是在同一个缓存行,那每次CPU内核之间通信时交换x,y值,可以同时共享两个值。是不是很高效?

    是的,一般情况下,如果x,y是属于数组内的数据 ,是可以达到高效共享数据的功能,但问题又来了:如果,x,y并不属于同一数组,x属于core1,而y属于core2,这个时候,如果core1更新了x,会导致y值失效了。为什么失效了,因为他们在同一缓存行。这时,只有把缓存行 flush到主存后, 其他内核中的相应的缓存行才会被置为过期数据,而缓存行什么时候flush到memory, 这个是有一定延时的 ,在这个延时当中, 其他CPU core是无法得知你的更新的 。那么内核core2再去读取Y的值时,由于L1的缓存里的数据已失效,那么就需要从L3获取,然后放入L2,再放入L1。 这样核心2读取Y值就需要从L3级的缓存读了。但是明明是内核core1修改的X的值,却影响到内核2去读取Y值了。同理,如果是内核2去修改Y的值,也会影响内核1去读取X的值。

    简单来说,x,y同放在缓存行,而且它们又属于不同CPU内核的数据值(事实上CPU内核也就是代码中的:线程)。那就会因为各自更新其中一个值,而导致缓存失效。

    这就是著名的伪共享问题。

    有没有什么解决方案呢?

    有的。

    方案是:缓存行填充。

     还是回到上面的例子,如果x,y同放到同一个缓存行,会造成伪共享。很简单,那就不要放在一起好了!

    比如:x有8byte(字节),而一般缓存行总共有64byte。那其他剩下的位置,我们就用预定的空变量填充就行了,代码如下 (java6版本):

      public final static class VolatileLong {
            public volatile long x= 0L;
            public long p1, p2, p3, p4, p5, p6,p7;     //缓存行填充
        }

     这个时候,core1更新x值,也就不会影响y值,从而造成伪共享问题。

    上面的代码是java6的解决方案。

    JAVA 8下的方案

    在JAVA 8中,缓存行填充终于被JAVA原生支持了。JAVA 8中添加了一个@Contended的注解,添加这个的注解,将会在自动进行缓存行填充,如下代码:

    import sun.misc.Contended;

    @Contended
    public class VolatileLong {
        public volatile long value = 0L;  
    }

    执行时,必须加上虚拟机参数-XX:-RestrictContended,@Contended注释才会生效。很多文章把这个漏掉了,那样的话实际上就没有起作用。

    这就是伪共享的解决方案,多么简单!

    本系列完毕!

    如果各位读者,还有什么意见或建议,欢迎拍砖吐槽!

    本人精通java高并发,DDD,微服务等技术实践,专注java,rust技术栈。 本人姓名郭莹城,坐标深圳,前IBM架构师、咨询师、敏捷开发技术教练,前IBM区块链研究小组成员、十多年架构设计工作经验,《区块链核心技术与应用》作者之一, 现聚焦于:区块链创投与交易所资源对接和技术咨询。 工作微信&QQ:360369487,区块链创投与交易所资源对接,加我注明:博客园+对接,技术咨询和顾问,加我注明:博客园+顾问。想学习golang和rust的同学,也可以加我微信,备注:博客园+golang或博客园+rust,谢谢!
  • 相关阅读:
    MAXSCRIPT 连数据库(转)
    Docker安装部署ELK教程 (Elasticsearch+Kibana+Logstash+Filebeat)
    ArrayList知识点
    HashMap知识点
    使用docker 安装nacos
    记录docker 安装sonarqube和安装的一些坑
    sql优化
    Centos7下安装Docker
    使用docker安装gitlab
    docker安装jenkins
  • 原文地址:https://www.cnblogs.com/gyc567/p/11029664.html
Copyright © 2011-2022 走看看