首页

专栏

java

文章详情

0

聊聊多线程中的伪共享现象

小强大人发布于 1 月 27 日

什么是伪共享?

讲伪共享之前,让我们先乘坐时光机,回到大学课堂,来重温下计算机组成原理的基础知识。我们知道,CPU和内存的运行速度相差很大,为了解决这个问题,在CPU和内存之间会加一级或多级高速缓存(Cache)。这个Cache一般是集成在CPU内部的,所以也叫CPU Cache。下图是一个两级Cache的CPU-Cache-内存架构。

数据在Cache中是按行存储的,其中每一行称为一个Cache行,如下图所示。它是CPU与内存数据交换的基本单位。Cache行的大小一般为2的幂次字节数。

CPU-Cache-内存架构的工作原理是这样的:当CPU访问某个变量时,首先会从CPU Cache里查看是否有该变量,如果有直接获取并返回,否则从内存中获取,然后把该变量所在内存区域的一个Cache行大小的内存数据复制到Cache中,这就是我们所说的局部性原理。由于存放到Cache行的不是单个变量,而是一个内存块数据,所以会出现多个变量放在一个Cache行中。当多个线程同时修改同一个缓存行里面的不同变量时,由于同一时刻只允许一个线程操作缓存行,一个线程成功获取缓存行的修改权时,其他线程会互斥等待,并且由于缓存一致性协议,其他线程相应的缓存行会失效,需要重新从内存获取数据,这无疑耗费了更多时间。所以相比把每个变量放在不同的缓存行,性能反而有所下降,这就是伪共享现象。

如下图所示,变量x,y放在同一个缓存行中,线程1操作缓存行的x变量,线程2操作y变量。

如何避免伪共享呢?

在JDK1.8之前,通常是通过字节填充的方式。什么意思呢?就是用到一个变量时,补充额外的若干辅助变量,使得这些变量刚好填充满一个缓存行,这样就避免了多个变量存放在一个缓存行中。具体看下面示例代码:

static final class PaddedLongField {

public volatile long value = 0L;

public long p1,p2,p3,p4,p5,p6;

}

假如CPU Cache行大小为64字节,那么我们这里填充了6个long型的变量,每个long型变量占8个字节,加上value一共7*8=56字节,另外,别忘了PaddedLongField是一个类对象,对象头还要占用8个字节,所以一个PaddedLongField对象占用64个字节,刚好填充满一个缓存行。

在JDK1.8之后,提供了一个@sun.misc.Contended注解,用来解决伪共享问题。此时,我们上面的代码就可以简化了:

@sun.misc.Contended

static final class PaddedLongField {

public volatile long value = 0L;

}

@Contended注解不仅可以修饰类,也可以修饰变量:

JUC源码里很多使用这个注解的,比如Thread类里threadLocalRandom相关的变量:

再比如LongAdder内部用到的Cell也用了这个注解:

再比如ForkJoinPool类上面也修饰了:

最后需要注意下,@Contended注解默认只用于Java核心类,比如rt.jar下的类。如果我们应用程序中想使用这个注解,需要添加一个JVM参数:-XX:-RestrictContended,填充的默认宽度为128字节,若需自定义宽度则可以用另一个参数:-XX:ContendedPaddingWidth=xxx

本文就到这里啦,若这篇文章对你有所帮助的话,点个赞再走叭!谢谢支持!

参考资料:

《Java并发编程之美》

java

阅读 53更新于 1 月 27 日

赞收藏

分享

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议

小强大人

9声望

1粉丝

关注作者

0 条评论

得票时间

提交评论

小强大人

9声望

1粉丝

关注作者

宣传栏

什么是伪共享?

讲伪共享之前,让我们先乘坐时光机,回到大学课堂,来重温下计算机组成原理的基础知识。我们知道,CPU和内存的运行速度相差很大,为了解决这个问题,在CPU和内存之间会加一级或多级高速缓存(Cache)。这个Cache一般是集成在CPU内部的,所以也叫CPU Cache。下图是一个两级Cache的CPU-Cache-内存架构。

数据在Cache中是按行存储的,其中每一行称为一个Cache行,如下图所示。它是CPU与内存数据交换的基本单位。Cache行的大小一般为2的幂次字节数。

CPU-Cache-内存架构的工作原理是这样的:当CPU访问某个变量时,首先会从CPU Cache里查看是否有该变量,如果有直接获取并返回,否则从内存中获取,然后把该变量所在内存区域的一个Cache行大小的内存数据复制到Cache中,这就是我们所说的局部性原理。由于存放到Cache行的不是单个变量,而是一个内存块数据,所以会出现多个变量放在一个Cache行中。当多个线程同时修改同一个缓存行里面的不同变量时,由于同一时刻只允许一个线程操作缓存行,一个线程成功获取缓存行的修改权时,其他线程会互斥等待,并且由于缓存一致性协议,其他线程相应的缓存行会失效,需要重新从内存获取数据,这无疑耗费了更多时间。所以相比把每个变量放在不同的缓存行,性能反而有所下降,这就是伪共享现象。

如下图所示,变量x,y放在同一个缓存行中,线程1操作缓存行的x变量,线程2操作y变量。

如何避免伪共享呢?

在JDK1.8之前,通常是通过字节填充的方式。什么意思呢?就是用到一个变量时,补充额外的若干辅助变量,使得这些变量刚好填充满一个缓存行,这样就避免了多个变量存放在一个缓存行中。具体看下面示例代码:

static final class PaddedLongField {

public volatile long value = 0L;

public long p1,p2,p3,p4,p5,p6;

}

假如CPU Cache行大小为64字节,那么我们这里填充了6个long型的变量,每个long型变量占8个字节,加上value一共7*8=56字节,另外,别忘了PaddedLongField是一个类对象,对象头还要占用8个字节,所以一个PaddedLongField对象占用64个字节,刚好填充满一个缓存行。

在JDK1.8之后,提供了一个@sun.misc.Contended注解,用来解决伪共享问题。此时,我们上面的代码就可以简化了:

@sun.misc.Contended

static final class PaddedLongField {

public volatile long value = 0L;

}

@Contended注解不仅可以修饰类,也可以修饰变量:

JUC源码里很多使用这个注解的,比如Thread类里threadLocalRandom相关的变量:

再比如LongAdder内部用到的Cell也用了这个注解:

再比如ForkJoinPool类上面也修饰了:

最后需要注意下,@Contended注解默认只用于Java核心类,比如rt.jar下的类。如果我们应用程序中想使用这个注解,需要添加一个JVM参数:-XX:-RestrictContended,填充的默认宽度为128字节,若需自定义宽度则可以用另一个参数:-XX:ContendedPaddingWidth=xxx

本文就到这里啦,若这篇文章对你有所帮助的话,点个赞再走叭!谢谢支持!

参考资料:

《Java并发编程之美》

java中什么是 伪共享_【Java】聊聊多线程中的伪共享现象相关推荐

  1. (单例设计模式中)懒汉式与饿汉式在多线程中的不同

    /*目的:分析一下单例设计模式中,懒汉式与饿汉式在多线程中的不同!开发时我们一般选择饿汉式,因为它简单明了,多线程中不会出现安全问题!而饿汉式需要我们自己处理程序中存在的安全隐患,但是饿汉式的程序技术 ...

  2. java中table是什么标签_[Java教程]javascript格式化table标签内容

    [Java教程]javascript格式化table标签内容 0 2015-07-12 20:00:08 项目中遇到这样的需求,一大段文章正文的html代码在手机中显示不全,原因是由于其它有table ...

  3. java实体类中有枚举类型_当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出?...

    当实体类中entity/DTO/VO等类中,有枚举值,应该怎么输出? 问题: orderStatus 和 payStatus都是枚举类,并且枚举的个数达地10来个,我们不可能在模板页面(jsp/ftl ...

  4. java中一级缓存二级缓存_[Java] hibernate 一级缓存和二级缓存

    缓存是介于应用程序和物理数据源之间,其作用是为了降低应用程序对物理数据源访问的频次,从而提高了应用的运行性能.缓存内的数据是对物理数据源中的数据的复制,应用程序在运行时从缓存读写数据,在特定的时刻或事 ...

  5. word中 有注释标签吗_如何在Word中注释图像

    word中 有注释标签吗 If you're writing a document that includes images, you may want to add annotations to t ...

  6. python中none是什么类型_如何在Python中”测试”None类型?

    我有一个方法,它有时返回一个非类型的值.那么我怎样才能质疑一个非类型的变量呢?例如,我需要使用if方法 if not new: new = '#' 我知道这是错误的方式,我希望你理解我的意思. 我想这 ...

  7. python取出矩阵中的某一元素_将tensorflow.Variable中的某些元素取出组成一个新的矩阵示例...

    在神经网络计算过程中,经常会遇到需要将矩阵中的某些元素取出并且单独进行计算的步骤(例如MLE,Attention等操作).那么在 tensorflow 的 Variable 类型中如何做到这一点呢? ...

  8. python中for循环怎么打开_详解Python中for循环的使用

    for 循环 本系列前面 "探索 Python,第 5 部分:用 Python 编程" 一文讨论了 if 语句和 while 循环,讨论了复合语句以及适当缩进 Python 语句来 ...

  9. python怎么从数组中提取连续的数字_从numpy数组中删除连续的数字

    我是python新手,但我对我要做的这个项目真的很困惑.我看到了一张图片here.我要做的是找出图像中像素范围从0到255的所有方块的平均值.下面的代码显示了我用来计算图片值的方法.出现的问题是像素/ ...

最新文章

  1. 5分钟 搭建免费个人博客
  2. Android报错:java.lang.IllegalArgumentException: Surface was abandoned
  3. python的源代码文件的扩展名是-python源文件后缀是什么?
  4. 事务控制语句,begin,rollback,savepoint,隐式提交的SQL语句
  5. 串口初始化结构体和固件库讲解
  6. 【渝粤教育】国家开放大学2018年秋季 0107-21T现代货币金融学 参考试题
  7. mvp的全称_是让人提神醒脑的 MVP、MVVM 关系精讲!
  8. mysql 不省略0_mysql数据类型和运算符
  9. Windows Server Update Service 3.0 配置向导
  10. python商城源码_腾讯大佬用了10小时讲完的Python,整整400集,拿走不谢
  11. python可视化迷宫求解_用python求解迷宫
  12. 最大连续子数组和 动态规划_53. 最大子序和(动态规划)
  13. 10grac修改public-ip vip-ip
  14. Java实现四则运算
  15. 据结构学习(冒泡、选择、插入、快速排....
  16. 虚拟机Ubuntu安装中文输入法
  17. jmeter将上一个接口的返回值作为下一个接口的参数
  18. 编写函数(fun),通过函数调用,输入存款金额和存款年限,计算到期总金额和利息。
  19. Win11上手初体验,文末附Win10升级Win11方法
  20. 人体经络气血运行规律

热门文章

  1. Java14来了!Switch竟如此简单?Lombok也不需要了?来用Idea搭建Java14吧!
  2. 如何让mysql索引更快一点
  3. 重置Oracle密码
  4. C#中利用Linq.Dynamic实现简单的动态表达式构建查询
  5. python3 上传文件到目标机器_Python3 +服务器搭建私人云盘,再也不怕限速了
  6. mysql 视图 教程_MySQL视图简介及基本操作教程
  7. python ansible模块_ansible常用模块
  8. 前端图片上坐标连线_前端图形学(十三)——弹跳运动的深入之傲娇的小球
  9. 拖拽的方式使用qbuttongroup_【无机纳米材料科研制图——Photoshop 0402】PS使用选框工具修改图片/图层...
  10. mysql数据库的三级模式_2016年计算机三级MySQL数据库试题