当前位置: 首页 > java >正文

volitale伪共享问题及解决方案

文章目录

      • 一、什么是伪共享?
      • 二、volatile与伪共享的关系
      • 三、伪共享的示例
      • 四、如何解决伪共享?
        • 1. 缓存行填充(Padding)
        • 2. Java 8的@Contended注解
      • 五、总结

在并发编程中,volatile关键字用于保证变量的可见性和禁止指令重排序,但它无法解决伪共享(False Sharing) 问题,甚至可能因volatile的内存语义加剧伪共享的性能影响。

一、什么是伪共享?

伪共享是CPU缓存机制导致的性能问题,根源在于CPU缓存行(Cache Line) 的工作方式:

  • CPU缓存以「缓存行」为最小存储单位(通常64字节),一个缓存行可以存储多个变量。
  • 当多个线程同时操作不同变量,但这些变量恰好位于同一缓存行时,会触发缓存一致性协议(如MESI),导致缓存行频繁失效和刷新,大幅降低性能。

简单说:无关变量共享了同一个缓存行,引发了不必要的缓存竞争,这就是伪共享。

二、volatile与伪共享的关系

volatile的内存语义会强化伪共享的影响:

  • volatile变量的修改会立即刷新到主存,并使其他CPU核心中该变量的缓存副本失效(通过缓存一致性协议)。
  • 若多个volatile变量位于同一缓存行,即使线程操作的是不同变量,也会因其中一个变量的修改导致整个缓存行失效,其他线程不得不重新从主存加载数据,产生额外开销。

三、伪共享的示例

假设两个线程分别修改VolatileData中的ab(均为volatile变量):

public class VolatileData {volatile long a;  // 8字节volatile long b;  // 8字节
}

由于ab总大小为16字节(远小于64字节缓存行),它们会被放入同一缓存行。此时:

  • 线程1修改a → 缓存行标记为失效 → 线程2读取b时,发现缓存行失效,必须从主存重新加载。
  • 线程2修改b → 缓存行再次失效 → 线程1读取a时又需重新加载。

这种频繁的缓存行失效会导致性能下降,这就是volatile变量引发的伪共享问题。

四、如何解决伪共享?

核心思路是让每个变量独占一个缓存行,避免多个变量共享缓存行。常见方案有两种:

1. 缓存行填充(Padding)

手动添加无用字段,填充缓存行剩余空间,使目标变量独占64字节缓存行。

示例(针对64字节缓存行,每个long占8字节):

public class VolatileDataWithPadding {volatile long a;// 填充7个long(56字节),加上a的8字节,共64字节,独占一个缓存行long p1, p2, p3, p4, p5, p6, p7;volatile long b;// 同样填充,让b独占另一个缓存行long p8, p9, p10, p11, p12, p13, p14;
}

此时ab分别位于不同缓存行,线程操作时不会相互影响。

2. Java 8的@Contended注解

Java 8引入@sun.misc.Contended注解(需JDK支持),自动为变量添加缓存行填充,无需手动写填充字段。

使用方式:

import sun.misc.Contended;public class VolatileDataWithContended {@Contended  // 自动填充,使a独占缓存行volatile long a;@Contended  // 自动填充,使b独占缓存行volatile long b;
}

注意:需添加JVM参数启用(默认仅用于JDK内部类):

-XX:-RestrictContended

五、总结

  • 伪共享是缓存行机制导致的性能问题,与变量是否为volatile无关,但volatile会加剧其影响。
  • 解决核心:让并发访问的变量各自独占一个缓存行(通过手动填充或@Contended)。
  • 适用场景:多线程高频读写不同变量的场景(如计数器、并发队列等),普通场景无需处理(会浪费内存)。

通过避免伪共享,可显著提升高并发场景下的性能(实测可能有10倍以上提升)。

http://www.xdnf.cn/news/18958.html

相关文章:

  • 高效管理远程连接!Remote Desktop Manager 全方位使用指南
  • 对接连连支付(四)-- 收款查询
  • 数据结构:单链表的应用(力扣算法题)第一章
  • 迅睿CMS自定义网站表单:HTML方式调用Select下拉选项数据指南
  • Winsock 操作指南
  • 宝塔面板零基础搭建 WordPress 个人博客与外贸网站 | 新手10分钟上手指南
  • vscode 调试 指定 python文件 运行路径
  • 嵌入式Linux自学不走弯路!670+讲课程!系统学习路线:入门+应用+ARM+驱动+移植+项目 (STM32MP157开发板)
  • Libvio访问异常排查指南
  • 《从有限元到深度学习:我的金属疲劳研究进阶之路》
  • Paimon——官网阅读:主键表
  • 【Kafka】项目整合使用案例
  • 解开 Ansible 任务复用谜题:过滤器用法、Include/Import 本质差异与任务文件价值详解
  • CPU 虚拟化之Cpu Models
  • 微算法科技(NASDAQ:MLGO)突破性FPGA仿真算法技术助力Grover搜索,显著提升量子计算仿真效率
  • 【LwIP源码学习7】ICMP部分源码分析
  • 【工具篇2】Gitee导入github repo作为持续的镜像站,自建 GitHub 镜像仓库详细步骤
  • Web转uni-app
  • 如何使用 Xshell 8 连接到一台 CentOS 7 电脑(服务器)
  • CellCharter | 入门了解
  • Linux 服务器故障全解析:常见问题及处理方法大全
  • imx6ull-驱动开发篇44——Linux I2C 驱动实验
  • PP工单状态JEST表
  • 浅聊达梦数据库物理热备的概念及原理
  • Ubuntu 切换 SOCKS5代理 和 HTTP 代理并下载 Hugging Face 模型
  • 三方相机问题分析八:【返帧异常导致性能卡顿】Snapchat后置使用特效预览出现卡顿
  • OpenTelemetry 在 Spring Boot 项目中的3种集成方式
  • 互联网大厂Java面试深度解析:从基础到微服务云原生的全场景模拟
  • 嵌入式linux相机(1)
  • CPU、IO、网络与内核参数调优