Java 中的伪共享详解及解决方案

转载自  Java 中的伪共享详解及解决方案

1. 什么是伪共享

CPU 缓存系统中是以缓存行(cache line)为单位存储的。目前主流的 CPU Cache 的 Cache Line 大小都是 64 Bytes。在多线程情况下,如果需要修改“共享同一个缓存行的变量”,就会无意中影响彼此的性能,这就是伪共享(False Sharing)。

2. 缓存行

由于共享变量在 CPU 缓存中的存储是以缓存行为单位,一个缓存行可以存储多个变量(存满当前缓存行的字节数);而CPU对缓存的修改又是以缓存行为最小单位的,那么就会出现上诉的伪共享问题。

Cache Line 可以简单的理解为 CPU Cache 中的最小缓存单位,今天的 CPU 不再是按字节访问内存,而是以 64 字节为单位的块(chunk)拿取,称为一个缓存行(cache line)。当你读一个特定的内存地址,整个缓存行将从主存换入缓存,并且访问同一个缓存行内的其它值的开销是很小的。

3. CPU 的三级缓存

由于 CPU 的速度远远大于内存速度,所以 CPU 设计者们就给 CPU 加上了缓存(CPU Cache)。 以免运算被内存速度拖累。(就像我们写代码把共享数据做Cache不想被DB存取速度拖累一样),CPU Cache 分成了三个级别:L1,L2,L3。越靠近CPU的缓存越快也越小。所 以L1 缓存很小但很快,并且紧靠着在使用它的 CPU 内核。L2 大一些,也慢一些,并且仍然只能被一个单独的 CPU 核使用。L3 在现代多核机器中更普遍,仍然更大,更慢,并且被单个插槽上的所有 CPU 核共享。最后,你拥有一块主存,由全部插槽上的所有 CPU 核共享。

当 CPU 执行运算的时候,它先去L1查找所需的数据,再去L2,然后是L3,最后如果这些缓存中都没有,所需的数据就要去主内存拿。走得越远,运算耗费的时间就越长。所以如果你在做一些很频繁的事,你要确保数据在L1缓存中。

4. 缓存关联性

目前常用的缓存设计是N路组关联(N-Way Set Associative Cache),他的原理是把一个缓存按照N个 Cache Line 作为一组(Set),缓存按组划为等分。每个内存块能够被映射到相对应的set中的任意一个缓存行中。比如一个16路缓存,16个 Cache Line 作为一个Set,每个内存块能够被映射到相对应的 Set 中的16个 CacheLine 中的任意一个。一般地,具有一定相同低bit位地址的内存块将共享同一个Set。 

下图为一个2-Way的Cache。由图中可以看到 Main Memory 中的 Index 0,2,4 都映射在Way0的不同 CacheLine 中,Index 1,3,5都映射在Way1的不同 CacheLine 中。

5. MESI 协议

多核 CPU 都有自己的专有缓存(一般为L1,L2),以及同一个 CPU 插槽之间的核共享的缓存(一般为L3)。不同核心的CPU缓存中难免会加载同样的数据,那么如何保证数据的一致性呢,就是 MESI 协议了。 

在 MESI 协议中,每个 Cache line 有4个状态,可用 2 个 bit 表示,它们分别是: M(Modified):这行数据有效,数据被修改了,和内存中的数据不一致,数据只存在于本 Cache 中; E(Exclusive):这行数据有效,数据和内存中的数据一致,数据只存在于本 Cache 中; S(Shared):这行数据有效,数据和内存中的数据一致,数据存在于很多 Cache 中; I(Invalid):这行数据无效。

那么,假设有一个变量i=3(应该是包括变量i的缓存块,块大小为缓存行大小);已经加载到多核(a,b,c)的缓存中,此时该缓存行的状态为S;此时其中的一个核a改变了变量i的值,那么在核a中的当前缓存行的状态将变为M,b,c核中的当前缓存行状态将变为I。如下图:

6. 解决原理

为了避免由于 false sharing 导致 Cache Line 从 L1,L2,L3 到主存之间重复载入,我们可以使用数据填充的方式来避免,即单个数据填充满一个CacheLine。这本质是一种空间换时间的做法。

7. Java 对于伪共享的传统解决方案

/***
* 微信公众号:Java技术栈
**/
import java.util.concurrent.atomic.AtomicLong;public final class FalseSharingimplements Runnable
{public final static int NUM_THREADS = 4; // changepublic final static long ITERATIONS = 500L * 1000L * 1000L;private final int arrayIndex;private static VolatileLong[] longs = new VolatileLong[NUM_THREADS];static{for (int i = 0; i < longs.length; i++){longs[i] = new VolatileLong();}}public FalseSharing(final int arrayIndex){this.arrayIndex = arrayIndex;}public static void main(final String[] args) throws Exception{final long start = System.nanoTime();runTest();System.out.println("duration = " + (System.nanoTime() - start));}private static void runTest() throws InterruptedException{Thread[] threads = new Thread[NUM_THREADS];for (int i = 0; i < threads.length; i++){threads[i] = new Thread(new FalseSharing(i));}for (Thread t : threads){t.start();}for (Thread t : threads){t.join();}}public void run(){long i = ITERATIONS + 1;while (0 != --i){longs[arrayIndex].set(i);}}public static long sumPaddingToPreventOptimisation(final int index){VolatileLong v = longs[index];return v.p1 + v.p2 + v.p3 + v.p4 + v.p5 + v.p6;}//jdk7以上使用此方法(jdk7的某个版本oracle对伪共享做了优化)public final static class VolatileLong{public volatile long value = 0L;public long p1, p2, p3, p4, p5, p6;}// jdk7以下使用此方法public final static class VolatileLong{public long p1, p2, p3, p4, p5, p6, p7; // cache line paddingpublic volatile long value = 0L;public long p8, p9, p10, p11, p12, p13, p14; // cache line padding}
}

8. Java 8 中的解决方案

Java 8 中已经提供了官方的解决方案,Java 8 中新增了一个注解: @sun.misc.Contended。加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在 jvm 启动时设置 -XX:-RestrictContended 才会生效。

@sun.misc.Contended
public final static class VolatileLong {public volatile long value = 0L;//public long p1, p2, p3, p4, p5, p6;

参考文献

http://igoro.com/archive/gallery-of-processor-cache-effects/ 

http://ifeve.com/false-sharing/ 

http://blog.csdn.net/muxiqingyang/article/details/6615199 

https://yq.aliyun.com/articles/62865


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328789.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ServletActionContext.getRequest().getSession() 和 ActionContext.getContext().getSession()的区别

ServletActionContext.getRequest().getSession() 和 ActionContext.getContext().getSession() ActionContext.getContext().getSession(); 这个方法获取的session是struts封装过的一个Map类型的session&#xff0c;只能调用put()方法缓存数据。 ServletActionContext.getRe…

弯下腰,拾起你无价的尊严

内容来源于网络&#xff0c;侵删&#xff01; 很久以前&#xff0c;一位挪威青年男子漂洋过海到了法国&#xff0c;他要报考著名的巴黎音乐学院。 考试的时候&#xff0c;尽管他竭力将自己的水平发挥到最佳状态&#xff0c;但主考官还是没能录取他。 身无分文的青年男子来到学…

在离线环境中发布.NET Core至Windows Server 2008

0x00 写在开始 之前一篇博客中写了在离线环境中使用.NET Core&#xff0c;之后一边学习一边写了一些页面作为测试&#xff0c;现在打算发布一下试试。看了下官方给出的发布教程感觉挺详细的了&#xff08;https://docs.asp.net/en/latest/publishing/iis.html&#xff09;&…

输入一个英文句子,翻转句子中单词的顺序 例如输入“I am a student.”,则输出“student. a am I”。

package com.atguigu.java; //输入一个英文句子&#xff0c;翻转句子中单词的顺序&#xff0c;但单词内字符的顺序不变。句子中单词以空格符隔开。 //为简单起见&#xff0c;标点符号和普通字母一样处理。 //例如输入“I am a student.”&#xff0c;则输出“student. a am …

Java 父类子类的对象初始化过程

转载自 Java 父类子类的对象初始化过程摘要: Java基本的对象初始化过程&#xff0c;子类的初始化&#xff0c;以及涉及到父类和子类的转化时可能引起混乱的情况。1. 基本初始化过程&#xff1a;对于一个简单类的初始化过程是&#xff1a;static 修饰的模块&#xff08;static变…

HttpServletRequest中getAttribute()和getParameter()的区别

一、数据据来源不同 HttpServletRequest类有setAttribute()方法&#xff0c;而 没有setParameter()方法get/setParameter是在对你的页面中的表单元素进行操作&#xff0c;获取的是这个表单元素中的值&#xff0c;是某个表单提交过去的数据get/setAttribute是对你页面中自己定义…

使用VS Code开发调试.NET Core 多项目

使用Visual Studio Code(VS Code)开发调试.NET Core和ASP.NET Core 多项目multiple project。 之前讲解过如果使用Visual Studio Code(VS Code) 开发单个.NET Core和ASP.NET Core项目&#xff0c;大家也都知道如何开发。 多项目可能有些人还不大了解&#xff0c;今天给大家介绍…

git 在ssh情况下提交代码

git --version --git版本 用户目录&#xff08;~/&#xff09; vim ~/.gitconfig --编辑用户目录&#xff08;~/&#xff09;下的 .gitconfig文件 --输入i 进入编辑模式 [user] nameRosen email1091947832qq.com [alias] --配置别名 cocheckout 切换分…

如果你也会C#,那不妨了解下F#(1):F# 数据类型

简单介绍 F#&#xff08;与C#一样&#xff0c;念作“F Sharp”&#xff09;是一种基于.Net框架的强类型、静态类型的函数式编程语言。可以说C#是一门包含函数式编程的面向对象编程语言&#xff0c;而F#是一门包含面向对象的函数式编程语言。可以查看官方文档了解更多信息。 本系…

String path = request.getContextPath()和String basePath = request.getScheme()

在JSP当中我们会用此代码来拼接路径&#xff0c;所以此语句是用来拼装当前网页的相对路径的。 <% String path request.getContextPath(); String basePath request.getScheme()"://"request.getServerName()":"request.getServerPort()path"/&…

Java中的函数传递

转载自 Java中的函数传递在C和C中&#xff0c;函数的传递可以通过函数指针来实现。在C#中&#xff0c;函数传递可以通过委托、Action、Func来实现。Java中没有函数指针、没有委托&#xff0c;那函数要如何传递呢&#xff1f; 可以通过以下两种方式实现。 1、通过handler&#…

使用Nginx搭建图片服务器(windows7)

1.进入官网下载nginx压缩包&#xff0c;解压后目录如下 2.在解压后的conf/nginx.conf配置文件中&#xff0c;添加添加或者修改带有颜色地方的代码 #user nobody; worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/e…

配置mybatis-plus逻辑删除

一、在pom文件里导入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>${mybatis.plus.boot.starter}</version> </dependency>二、在yml文件或者在properties…

Jexus支持HTTPS协议

众所周知&#xff0c;在HTTPS页面请求HTTP资料的时候&#xff0c;现代浏览器会拦截&#xff0c;提示用户是否继续&#xff0c;或者直接拦截&#xff0c;提示都不出来。 最近给自己做了个快速书签工具&#xff0c;点击书签就直接把书签发送到服务器地址&#xff0c;然后保存到我…

java面向对象高级分层实例_实体类

package bdqn.studentSys.entity; /*** 学生实体类* author Administrator**/ public class Student {private String name;//姓名private String pwd;//密码private int age;//年龄private int stuno;public int getStuno() {return stuno;}public void setStuno(int stuno) {…

虚拟机安装xp经验

虚拟机安装xp经验 1.打开vm软件 2.创建虚拟机 选中单个文件 用pe系统打开 3.用驱动精灵 创建2个分区50g 50g (一定要创建2个gost版要在d盘分配文件夹的)分配8g内存 4核cpu 4.将系统安装到分区上

JAVA实现汉字转换为拼音 pinyin4j/JPinyin

转载自 JAVA实现汉字转换为拼音 pinyin4j/JPinyin在项目中经常会遇到需求用户输入汉字后转换为拼音的场景&#xff0c;比如说通讯录&#xff0c;就会要求按名字首字符发音排序&#xff0c;如果自己写实现这方面的功能是个很好大的工程&#xff0c;还好网上有公开的第三方jar支…

给数据库表字段设置默认值

一、在数据库表中的操作方法 当表中的字段是varchar字段时可以这样设置&#xff1a; 例如我要设置stats&#xff08;状态这个字段默认为"1"&#xff09;&#xff0c;在创建表的时候sql语句可以这样写 stats varchar(1) CHARACTER SET utf8 NOT NULL DEFAULT 1 COMM…

Myeclipse创建第一个web项目

创建web项目 web project 创建java项目 选java project

使用cardme读写VCard文件,实现批量导入导出电话簿

转载自 使用cardme读写VCard文件&#xff0c;实现批量导入导出电话簿首先下载jar包cardme。 http://sourceforge.net/projects/cardme/?sourcenavbar cardme是基于java语言的操作vCard(后缀vcf)文件的开源项目。 在项目中有一个类net.sourceforge.cardme.engine.TestParser。是…