天增的博客
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
首页
博客
  • 分布式解决方案
  • Java并发工具包
  • redis
  • LeetCode
  • 系统设计
  • JVM体系
Github (opens new window)
Rss (opens new window)
  • zh-CN
  • en-US
  • Java并发工具包
  • 并发基础
    • 线程基础
      • Thread的状态
      • 进程与线程
      • 正确停止线程的方式
      • Thread的实现方式
      • waitnotifynotifyAll
      • 生产者消费者模型
    • 线程安全
      • 线程不安全
      • 线程安全
      • 需要注意线程安全问题的情况
  • 并发工具
    • 线程协作
      • Semaphore信号量
      • CountDownLatch详解
      • 使用CompletableFuture解决旅游平台问题
      • 使用CyclicBarrier解决团建问题
    • Future
      • Future主要功能
      • FutureTask源码分析
    • ThreadLocal
      • ThreadLocal内存泄漏
      • ThreadLocal使用场景
    • 原子类
      • 原子类的作用概览
      • 原子类的性能分析
    • 阻塞队列
      • 常见的阻塞队列
      • 阻塞队列的常用方法
      • 什么是阻塞队列
    • 并发容器
      • HashMap
      • CopyOnWriteArrayList
      • ConcurrentHashMap详解
    • 线程池
      • 为什么多线程会带来性能问题
      • 线程池的优势
      • 创建线程池的参数
        • 如何设置线程数
      • 线程池线程复用原理
      • ForkJoin框架
    • 各种锁
      • 锁的种类和特点
        • 公平锁非公平锁
        • 自旋锁非自旋锁
        • 共享锁独占锁
        • 乐观锁和悲观锁
      • JVM锁优化
      • synchronized和Lock的对比
      • lock的常用方法
  • 底层原理
    • CAS原理
    • AQS框架
    • 伪共享
      • 什么是伪共享
      • JVM中的伪共享
    • java内存模型
      • Java内存模型介绍
      • happens-before规则
  • topic
  • Java并发工具包
  • 底层原理
  • 伪共享
2022-04-21
目录

伪共享

# 伪共享

我们在看ForkjoinPool源码的时候可以看到class上加上了@sun.misc.Contended的注解。

这个注解,在业务中鲜有用到,不过在极端并发倒是常用。

这个注解就是为了避免进行"伪共享"。

# 什么是伪共享

伪共享的诞生是有因为有CPU缓存。

现代CPU的架构:

CPU

CPU和内存之间有个缓存的概念。有L1、L2、L3三种等级,离CPU越近容量越小、速度越快;离CPU越远容量越大、速度越慢;

L1、L2集成在CPU上,L3集成在主板上。

CPU是通过缓存行来进行缓存的,大小为2的整数幂,主流大小为64个字节。

如果,多个变量同属于同一个缓存行,在并发情况下修改的时候,由于写屏障和内存一致性协议,导致同一时间只有一个线程能对这个缓存行进行操作,进而导致竞争性能下降。这就是伪共享的概念,原本不应该共享的变量,由于硬件的特性导致性能下降。

# JVM中的伪共享

一个 Java 的 long 类型是 8 字节,因此在一个缓存行中可以存 8 个 long 类型的变量。

伪共享

在程序运行的过程中,缓存每次更新都从主内存中加载连续的 64 个字节。

因此,如果访问一个 long 类型的数组时,当数组中的一个值被加载到缓存中时,另外 7 个元素也会被加载到缓存中。

假如,我们使用的是数组进行存储的话,这种自动加载机制可以带来新能上的提升。

但是,也就意味着,假如要加载变量A的话,不是数组而是单独的一个变量,这时相邻的有个B变量,B变量也将被载入进来。

当前者修改 a 时,会把 a 和 b 同时加载到前者核心的缓存行中,更新完 A 后其它所有包含 A 的缓存行都将失效,因为其它缓存中的 A 不是最新值了。

而当后者读取 B 时,发现这个缓存行已经失效了,需要从主内存中重新加载。

这样就出现了一个问题,A 和 B完全不相干,每次却要因为 A 的更新需要从主内存重新读取,它被缓存未命中给拖慢了。

# 如何避免

就是利用@sun.misc.Contended注解,这个注解是java1.8之后提出的。

加上这个注解的类会自动补齐缓存行,需要注意的是此注解默认是无效的,需要在 jvm 启动时设置 -XX:-RestrictContended 才会生效。

比如原本变量A的长度仅有8个字节,它会前后帮我们补上54个字节。这样我们的变量就能独占一个缓存行,也就避免了多线程同时更新同一个缓存行带来的伪共享问题。

最近更新
01
以 root 身份启动 transmission-daemon
12-13
02
Debian系统安装qbittorrent-nox
12-09
03
LXC Debain12安装zerotier并实现局域网自动nat转发
07-29
更多文章>
Theme by Vdoing | Copyright © 2015-2024 天增 | 苏ICP备16037388号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式