从kernel层面分析synchronized、volatile,进大厂必备硬核小伎俩(上)

2023-05-16

	synchronized、volatile对于java程序员来说再熟悉不过了,但
是你知道这两个关键字底层是如何实现的吗(甚至在操作系层面是
通过什么指令来实现的)?以及与其相关的术语:诸如用户态与内核
态、cas、锁升级、内存一致性协议、内存屏障都是什么,下面我来一
一揭秘。本专题将分为两篇文章进行讲解,此篇主要介绍关于
synchronized和volatile在kernel层面涉及的一些核心概念,下一篇
会详细说明synchronized和volatile实现原理,包括内存屏障、
锁升级过程(偏向、轻量、重量)、重入锁、线程可见性、指令重排等
核心原理,其中也不乏DCL单例是否需要volatile修饰等有趣问题。

用户态和内核态

一般的操作系统对操作指令进行了权限控制,在intel x86 cpu中将级别分为0-3,0为最高执行权限,3为最低执行权限,0和3分别代表内核态和用户态,简单来说就是需要与操作系统,比如操作系统硬件打交道时,就需要调用内核态来完成,而jvm是运行在用户态的,或者说运行在用户空间的。

运行在用户空间的是干活的,权利小,内核空间才是操作系统老大,做
大事需要向内核空间申请权限。

CAS

compare and swap(比较并交换),即有2个线程A和B,同时对int i = N进行加1操作,当线程A将i+1后的结果写入主内存赋值给i前,首先会比较当前主内存中i的值是否为N,如果为N就执行赋值操作,否则有可能是B线程执行了对i的修改操作(如目前i=N+1),A线程就不执行赋值操作,A线程再次从主内存中读取最新的i的值,然后再执行i+1操作,然后再次将结果赋值给i,在赋值之前同样比较当前主内存中的i的值是否为N+1,如果为N+1,就将线程A修改的i的值赋值给i,否则再次执行上述操作,直至修改成功为止。我们可以把这个操作成为自旋操作。

AtomicInteger就是cas的实现:

public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

也就是最后会调用一个native方法compareAndSwapInt函数。这个方法依次会调用操作系统代码unsafe.cpp,atomic.cpp和atomic_windows_x86.inline.hpp/atomic_linux_x86.inline.hpp:

// Adding a lock prefix to an instruction on MP machine
// VC++ doesn't like the lock prefix to be on a single line
// so we can't insert a label after the lock prefix.
// By emitting a lock prefix, we can define a label after it.
#define LOCK_IF_MP(mp) __asm cmp mp, 0  \
                       __asm je L0      \
                       __asm _emit 0xF0 \
                       __asm L0:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {
  // alternative for InterlockedCompareExchange
  int mp = os::is_MP();
  __asm {
    mov edx, dest
    mov ecx, exchange_value
    mov eax, compare_value
    LOCK_IF_MP(mp)
    cmpxchg dword ptr [edx], ecx
  }
}

上面的代码就是操作系统级别执行cas操作执行的指令代码,简单来说就是会执行lock_if_mp cmpxchg来完成cas操作的。其中cmpxchg就是执行cas操作的,lock_if_mp是指如果是多核cpu,就将cmpxchg指令进行加锁执行,否则就不加锁。从而可以看出cmpxchg并不是原子操作,需要加lock后才是原子操作,其实锁住的是北桥信号。上面就是cas在操作系统层面的执行原理。

缓存行


上面是最常见的缓存简图。main cache为主内存,L1 cache、L2 cache、L3 cache分别为1级缓存、2级缓存、3级缓存。cahe line为缓存行。那么缓存行是什么?缓存行就是保存了我们需要执行的指令代码,大多数一个缓存行为64个字节,cpu一次也会读取64个字节指令到cpu中进行使用。

下面我们来看一个比较有意思的两段代码片段:
代码片段1:

public class TestCacheLingOne {

    static volatile Long[] l = new Long[2];

    public static void main(String[] args) throws InterruptedException {

        Thread threadOne = new Thread(){
            @Override
            public void run() {
                for (long i=0;i<1000_000_000L;i++) {
                    l[0] = 1L;
                }
            }
        };
        Thread threadTwo = new Thread(){
            @Override
            public void run() {
                for (long i=0;i<1000_000_000L;i++) {
                    l[1] = 2L;
                }
            }
        };
        long time = System.currentTimeMillis();
        threadOne.start();
        threadTwo.start();
        threadOne.join();
        threadTwo.join();

        System.out.println((System.currentTimeMillis() - time) /1000d);
    }
}

在这里插入图片描述
代码片段2:

public class TestCacheLineTwo {

   static volatile Long[] l = new Long[9];

   public static void main(String[] args) throws InterruptedException {

       Thread threadOne = new Thread(){
           @Override
           public void run() {
               for (long i=0;i<1000_000_000L;i++) {
                   l[0] = 1L;
               }
           }
       };
       Thread threadTwo = new Thread(){
           @Override
           public void run() {
               for (long i=0;i<1000_000_000L;i++) {
                   l[8] = 2L;
               }
           }
       };
       long time = System.currentTimeMillis();
       threadOne.start();
       threadTwo.start();
       threadOne.join();
       threadTwo.join();

       System.out.println((System.currentTimeMillis() - time) /1000d);
   }
}

在这里插入图片描述
上面的两段程序都是2个线程对同一个volatile修饰的Long类型数组进行10亿次值修改操作,那么执行用时差别还是很明显的,这是为什么呢?这是由于cpu读取缓存行字节数和下面我要说的缓存一致性有关,在下一篇内容中会进行具体说明。

缓存一致性协议

其实缓存一致性协议和synchronized、volatile本身没有关系,缓存一致性是操作系统层面的概念。目前一致性协议有MESI、MSI、Dragon等等,我们常说的MESI其实是intnel x86的缓存一致性协议,MESI即:Modified(修改)、Exclusive(独享)、Shared(共享)、Invalid(失效),指的就是缓存行的4种状态。

在这里插入图片描述
上图说明了2个线程对主内存中同一个缓存行的操作过程。

  • cpu1即线程1,读取主内存中的数据x,将x写入当前线程缓存,缓存行状态为E(共享),同时cpu1监听总线(嗅探)。
  • 此时cpu2即线程2读取主内存中的数据x,此时cpu1监听器发现线程2读取了主内存中的x缓存行,则将cpu1中的x缓存行置为S(共享)
  • 线程2将x缓存行从主内存读取到线程2缓存中,将x缓存行状态置为S(共享),并监听总线。
  • 此时线程1修改了x值,通知总线,此时将线程2中的x缓存行置为I(无效),将线程1中的x缓存行置为E(独享)。
  • 此时线程2如果想操作x,那么需要再次从主内存中读取x缓存行。

CPU指令乱序

cpu指令乱序是指cpu为了提高其执行效率,在操作系统层面会将指令乱序执行(不是按照程序编写顺序执行)。但是这种cpu指令乱序会导致在多线程情况下,产生问题。看如下代码,证明cpu存在乱序执行:

public class Test {

    static int a,b,c,d;
    public static void main(String[] args) throws InterruptedException {
        int x = 0;
        for(;;) {
            x++;
            a = 0;
            b = 0;
            c = 0;
            d = 0;

            Thread threadOne = new Thread(new Runnable() {
                @Override
                public void run() {
                    a = 1;
                    c = b;
                }
            });
            Thread threadTwo = new Thread(new Runnable() {
                @Override
                public void run() {
                    b = 1;
                    d = a;
                }
            });

            threadOne.start();
            threadTwo.start();
            threadOne.join();
            threadTwo.join();

            if (c == 0 && d == 0) {
                System.out.println("执行第" + x + "次,证明cpu是可以乱序执行的");
                break;
            }

        }
    }
}

在这里插入图片描述
如果cpu没有指令乱序执行存在,那么不可能打印出同时满足c=0,d=0,所以说cpu存在乱序问题。

最后

下一篇将接着本篇讲解,会介绍内存屏障问题,及锁升级过程、线程可见性、指令重排等相关技术。如果本篇对你有用,欢迎点赞、关注、转载,由于水平有限,如有问题请留言。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从kernel层面分析synchronized、volatile,进大厂必备硬核小伎俩(上) 的相关文章

  • 【强烈推荐】基于STM32的TFT-LCD各种显示实现(内容详尽含代码)

    前言 xff1a TFT LCD模块作为人们日常生活中常见屏幕类型之一 xff0c 使用的受众面非常广阔 例如 xff1a 显示各个传感器数值 xff0c 显示精美界面 xff0c 多级化菜单系统等等都不离不开他的身影 可以说学会TFT L
  • 时序数据库-4-[IoTDB]的python3操作

    从采集到存储 xff1a 时序数据库到底怎么处理时间 xff1f iotdb官方文档手册 1 容器安装iotdb 可以使用docker volume create命令创建 docker 卷 此命令将在 var lib docker volu
  • [汇总]基于ESP32的四旋翼无人机开发纪实

    文章目录 一 项目说明1 已实现功能2 硬件配置 二 ESPlane2 0 开发笔记三 相关传感器驱动移植四 参考链接 ESPlane 项目更名为 ESP Drone 现已公开代码仓库和文档 代码仓库 xff1a https github
  • [填坑]Ubuntu安装显卡专有驱动后鼠标键盘无法使用

    问题描述 我在两个地方遇到了同样的问题 xff0c 解决方法也如出一辙 xff0c 由于没有研究源码 xff0c 暂不清楚原因 问题1描述 xff1a 为了解决Ubuntu下笔记本功耗问题 xff0c 在网友建议下我安装了bumblebee
  • uniapp-前后端开发app-系列01开篇

    系列文章目录 文章目录 系列文章目录前言一 开发工具 xff1f 二 项目架构三 具体内容实现 前言 提示 xff1a 这里可以添加本文要记录的大概内容 xff1a 随着app和小程序的发展 有没有开发一个模版 其他端程序都能用 uniap
  • TypeError: iter() returned non-iterator of type

    在使用Python迭代器时出现错误 xff1a class Fibs def init self self a 61 0 self b 61 1 def next self self a self b 61 self b self a 43
  • 【Linux应用编程】一个异步信号处理引起死锁问题的思考

    文章目录 1 前言2 为什么会产生死锁2 1 死锁2 2 分析2 3 结论 3 避免死锁4 举一反三5 死锁例子代码6 参考文章 1 前言 最近在维护别人的代码时 xff0c 遇到一个线程死锁问题 xff0c 一番折腾 xff0c 最终定位
  • 【RT-Thread】SGM706独立看门狗软件包

    文章目录 1 简介1 1 目录结构1 2 许可证 2 芯片介绍3 支持情况4 使用说明4 1 依赖4 2 获取软件包4 3 初始化4 4 启动看门狗4 5 msh finsh测试查看设备注册通过msh启动看门狗 5 注意事项6 联系方式 1
  • 利用tldr工具再也不怕记不住Linux命令

    文章目录 1 前言2 tldr3 安装4 使用 1 前言 linux命令非常多 xff0c 少用的命令往往易忘记 xff0c 甚至常用的语法较为复杂的命令也不好记住 当然有些太复杂的命令也不需要死记硬背 xff0c 我们往往会借助man命令
  • C++中的二阶构造函数

    文章目录 1 前言2 二阶构造3 总结 1 前言 构造函数用于创建对象时对象成员的初始化 xff0c 如赋初值 申请内存 加载文件等 xff0c 即是自动完成对象的初始化任务 在C 43 43 语言中 xff0c 构造函数执行顺序是 xff
  • open函数簇与fopen函数簇区别和用法

    文章目录 1 前言2 open与fopen区别2 1 标准不同2 2 层次不同2 3 适用对象不同 xff08 返回值不同 xff09 2 4 缓冲区2 5 效率不同 3 使用方法3 1 open3 2 fopen 1 前言 linux系统
  • 基于STM32的OLED多级菜单GUI实现(简化版智能手表)

    前言 xff1a 本文的OLED多级菜单UI 为一个综合性的STM32小项目 xff0c 使用多传感器 与OLED显示屏 实现智能终端 的效果 项目中的多级菜单UI使用了较为常见的结构体索引法 去实现功能与功能之间的来回切换 xff0c 搭
  • 【RTD】铂电阻测温原理与具体方法

    文章目录 1 基本原理2 铂电阻2 1 铂电阻测温原理2 2 铂电阻类型和测量方法2 2 1 两线式铂电阻2 2 2 三线式铂电阻2 2 3 四线式铂电阻 3 小结 相关文章 xff1a RTD 铂电阻测温原理与具体方法 RTD AD779
  • 【RTD】AD7793三线式铂电阻PT100/PT1000应用

    文章目录 1 AD7793简介2 AD7793 三线式铂电阻测量2 1 阻值计算 3 小结 相关文章 xff1a RTD 铂电阻测温原理与具体方法 RTD AD7793三线式铂电阻PT100 PT1000应用 RTD AD7793四线式铂电
  • 【RTD】AD7793四线式铂电阻PT100/PT1000应用

    文章目录 1 前言2 AD7793 四线式铂电阻测量2 1 阻值计算 3 小结 1 前言 上一篇文章描述的是RTD驱动芯片AD7793特点 xff0c 以及其与三线式RTD连接使用方法 本文描述四线式RTD与AD7793的使用 相关文章 x
  • 【RTD】AD7793两线式铂电阻PT100/PT1000应用

    文章目录 1 前言2 AD7793 两线式铂电阻测量2 1 阻值计算 3 小结 1 前言 上一篇文章描述的是RTD驱动芯片AD7793与四线式RTD连接使用方法 本文描述两线式RTD与AD7793的使用 相关文章 xff1a RTD 铂电阻
  • 【RTD】AD7793驱动程序

    文章目录 1 前言2 AD7793驱动程序2 1 spi访问接口2 2 寄存器和常用配置值2 3 初始化2 4 原始数据获取2 5 阻值换算 3 使用4 完整工程代码 1 前言 前面文章主要描述AD7793分别与两线 三线 四线RTD连接电
  • 【RTD】二分法查找和分段线性插值算法在RTD中应用

    文章目录 1 前言2 二分法查找2 1 复杂度2 2 实现 3 分段线性插值4 RTD实例 1 前言 处理器通过RTD采集电路 xff08 芯片 xff09 精确获得当前RTD电阻值后 xff0c 再结合RTD与温度线性关系表 xff0c
  • 24系列EEPROM/FRAM通用驱动库移植到RT-Thread

    文章目录 1 前言2 接口实现2 1 i2c收发函数实现2 2 页写延时函数2 3 写保护函数2 4 设备注册 3 对接RT Thread设备驱动3 1 标准设备驱动接口3 2 注册到RT Thread3 3 导出到msh3 4 测试 4
  • 【RT-Thread】TCA9534 8位I/O扩展器驱动软件包

    文章目录 1 简介1 1 目录结构1 2 许可证 2 芯片介绍3 支持情况4 使用说明4 1 依赖4 2 获取软件包4 3 初始化4 4 访问设备4 5 msh finsh测试查看设备注册执行sample 5 代码仓库 1 简介 tca95

随机推荐

  • 【代码质量】RAII在C++编程中的必要性

    文章目录 1 前言2 什么是RAII3 为什么用RAII4 RAII应用5 小结 1 前言 C C 43 43 相比其他高级编程语言 xff0c 具有指针的概念 xff0c 指针即是内存地址 C C 43 43 可以通过指针来直接访问内存空
  • C++ RAII典型应用之lock_guard和unique_lock模板

    文章目录 1 前言2 lock guard3 lock guard使用4 unique lock5 相关文章 1 前言 常用的线程间同步 通信 xff08 IPC xff09 方式有锁 xff08 互斥锁 读写锁 自旋锁 xff09 屏障
  • 基于STM32的实时操作系统FreeRTOS移植教程(手动移植)

    前言 xff1a 此文为笔者FreeRTOS专栏 下的第一篇基础性教学文章 xff0c 其主要目的为 xff1a 帮助读者朋友快速搭建出属于自己的公版FreeRTOS系统 xff0c 实现后续在实时操作系统FreeRTOS上的开发与运用 操
  • 通过sysinfo获取Linux系统状态信息

    系统运行状态信息是我们关注的重点 xff0c 通过当前系统的输出信息 xff0c 如内存大小 进程数量 运行时间等 xff0c 以便分析CPU负载 软硬件资源占用情况 xff0c 确保系统高效和稳定 Linux系统中 xff0c 提供sys
  • Keil AC5/Keil AC6/IAR指定数据绝对存储地址

    文章目录 1 前言2 实现方法3 例子 1 前言 编译过程中 xff0c 指定数据绝对存储地址在实际项目中会经常使用到或者必须用到 xff0c 这样使得项目实现某些功能可以非常灵活 xff0c 常用的场景有 xff1a IAP升级时候 xf
  • 嵌入式开发常用到的在线工具

    文章目录 IP地址计算常用加解密 xff0c AES DSE Base64 MD5异或 xff08 BCC xff09 校验CRC计算十六进制格式化字符串Json格式化HTML运行器常用在线编译器 xff08 C C 43 43 C JAV
  • STM32H7xx 串口DMA发送&接收(LL库)

    文章目录 1 前言2 STM32H7实现2 1 关键步骤2 2 注意事项 3 代码仓库 1 前言 关于串口DMA收发实现 xff0c 不同CPU其套路都是类似的 xff0c 不同之处在于寄存器配置 依赖BSP库等差异 串口DMA收发详细实现
  • 正交编码器溢出处理

    文章目录 1 正交编码器1 1 参数特性1 2 应用范围 2 正交编码器使用2 1 溢出问题2 2 中断模式2 3 循环模式延伸 1 正交编码器 正交编码器一般指的是增量式光栅 xff08 磁栅 xff09 编码器 xff0c 通常有三路输
  • PX4多旋翼期望姿态矩阵生成算法

    1 PX4多旋翼期望姿态生成算法 1 1 求期望体轴X轴向量1 2 求期望体轴Y轴向量1 3 求期望姿态矩阵1 4 求期望姿态角 1 PX4多旋翼期望姿态生成算法 PX4多旋翼期望姿态生成采用旋转矩阵方法 xff0c 基本思路为根据外环解算
  • git安装

    Git介绍 分布式 xff1a Git版本控制系统是一个分布式的系统 xff0c 是用来保存工程源代码历史状态的命令行工具 保存点 xff1a Git的保存点可以追踪源码中的文件 并能得到某一个时间点上的整个工程项目的状态 xff1b 可以
  • linux更改ssh连接方式将publickey改为用户名密码登录

    1 vim etc ssh sshd config 2 PermitRootLogin no 改为 PermitRootLogin yes 3 service restart sshd
  • dsp2812 pmsm foc之速度环电流环

    61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 速度环PI 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
  • leetcode(c++)

    放假没事刷几道leetcode xff0c 一些常见典型题的答案和解析 平时python用的比较多 xff0c 但分析复杂度的时候用python编程不方便 xff0c 所以刷题的时候用了c 43 43 C 43 43 基础 C 43 43
  • 基于STM32的超声波雷达项目【可拟合构建平面地图】(代码开源)

    前言 xff1a 本文为手把手教学基于STM32的超声波雷达 项目 HC SR04雷达 本次项目采用的是STM32作为MCU xff0c 搭配常用的HC SR04超声波模块与舵机SG90实现模拟雷达检测 的效果 模拟了雷达图UI 可以拟合构
  • android sdk manager不显示更新,只显示已安装,解决办法

    启动 Android SDK Manager xff0c 打开主界面 xff0c 依次选择 Tools Options xff0c 弹出 Android SDK Manager Settings 窗口 xff1b 在 Android SDK
  • detectron2学习:KeyError: “No object named ‘XXXXX‘ found in ‘BACKBONE‘ registry!“

    问题来源 在使用FB的框架detectron2改写模型的时候碰到了KeyError 34 No object named 39 XXXXX 39 found in 39 BACKBONE 39 registry 34 的bug 分析 xff
  • linux内核网络协议栈--linux网络设备理解(十三)

    网络层次 linux网络设备驱动与字符设备和块设备有很大的不同 字符设备和块设备对应 dev下的一个设备文件 而网络设备不存在这样的设备文件 网络设备使用套接字socket访问 xff0c 虽然也使用read write系统调用 xff0c
  • makefile使用--命令(三)

    一 Make的概念 Make这个词 xff0c 英语的意思是 34 制作 34 Make命令直接用了这个意思 xff0c 就是要做出某个文件 比如 xff0c 要做出文件a txt xff0c 就可以执行下面的命令 span class t
  • 【笔记】ubuntu18.04 ros melodic turtlebot3 源码下,导航gmapping仿真

    编写本笔记原因 xff1a 源码编译没问题 xff0c 但是在运行roslaunch turtlebot3 slam turtlebot3 slam launch slam methods 61 cartgrapher时出现下面这个错误 x
  • 从kernel层面分析synchronized、volatile,进大厂必备硬核小伎俩(上)

    synchronized volatile对于java程序员来说再熟悉不过了 xff0c 但 是你知道这两个关键字底层是如何实现的吗 xff08 甚至在操作系层面是 通过什么指令来实现的 xff09 xff1f 以及与其相关的术语 xff1