首页 > 编程语言 >JUC源码解析:深入理解 volatile

JUC源码解析:深入理解 volatile

时间:2024-05-11 22:43:24浏览次数:27  
标签:JUC 屏障 Object instance 源码 volatile 内存 线程

JUC源码解析:深入理解 volatile

volatile 的定义

volatile 的作用:

  • 保证可见性
  • 禁止指令重排序

volatile 可以被看作是轻量版的 synchronized,volatile 保证了多线程中共享变量的“可见性”,就是说,当volatile 变量的值被修改时,其他线程能读取到被修改的变量值。

如果volatile 使用恰当的话,它比synchronized的执行成本更低,因为volatile 不需要线程上下文的切换,并且在“读多写少”的情况下,volatile的效率更好。

为什么在“读多写少”的情况下,volatile的效率更好?

因为在 volatile 的JMM内存屏障中定义,如果发生对 volatile 变量的读操作,会在 读之后 设置内存屏障,而读之前是没有内存屏障的。在 写前后 却都有内存屏障。所以读多写少的情况下volatile的效率会更高些。

关于JMM内存屏障,请看下一节的介绍

volatile 禁止指令重排序原理

volatile 会在编译器生成字节码时,插入JMM内存屏障来保障 volatile 变量不被重排序。

JMM有四个volatile内存策略:

  • 写操作:
    • 写之前插入 StroeStore 屏障
    • 写之后插入 StoreLoad 屏障
  • 读操作:
    • 读之后插入 LoadLoad 屏障
    • 读之后插入 LoadStore 屏障

这是非常保守的屏障策略,实际中,如果有这样操作:

//伪代码
volatile int a, b;
a = 1; // volatile 写操作
b = 1; // volatile 写操作

两个写操作在一起时,JMM可以不那么保守。因为两个写操作并在一起,可以在中间省略一些屏障,让程序更有效率。

单例模式线程不安全的核心原因

​ 有些单例模式是线程不安全的,为什么,看看下面的代码:

public class Main {

    private static Object instance;

    public static Object getInstance() {

        if (instance == null) {	// 第一次检查
            synchronized (Main.class) {	// 加锁
                if (instance == null) { // 第二次检查
                    instance = new Object(); // 问题的根源出现在这里!!!
                }
            }
        }

        return instance;
    }
}

很显然,这是一个不适应 volatile 的线程不安全的单例模式。为什么非线程安全,核心漏洞在 instance = new Object();这一行语句上,我们把它拆解为下面三行伪代码:

memory = allocate(); // 1.为对象分配内存空间
ctorInstance(memory); // 2. 初始化对象
instance = memory; // 3. 设置instance指向刚分配的地址

记住,此时的instance是没有volatile关键字的,它是允许被重排序的,在一些多线程情况加,它可能会被重排序成这种情况

memory = allocate(); // 1.为对象分配内存空间
instance = memory; // 3. 设置instance指向刚分配的地址
ctorInstance(memory); // 2. 初始化对象

还没有在内存中初始化对象,就已经分配地址了!

这时候,如果有另外一个线程钻空子,就可能拿到一个分配了内存地址、但还没有真正初始化的对象

因此,单例模式在多线程环境下,使用 volatile 禁止重排序是必要的!


使用 volatile 优化后的代码:

public class Main {

    private static volatile Object instance;

    public static Object getInstance() {

        if (instance == null) {	
            synchronized (Main.class) {
                if (instance == null) { 
                    instance = new Object(); // 现在不会被重排序了
                }
            }
        }

        return instance;
    }
}

深层解读:

instance = new Object(); 是 volatile 写操作,JMM会在编译期间为它设置内存屏障。在这句代码之前,会加入 storestroe 屏障,在这句代码之后,会加入 storeload 屏障,这样,其他线程就必须在 stroeload 屏障后面读取,就保证了创建对象时的原子性。

标签:JUC,屏障,Object,instance,源码,volatile,内存,线程
From: https://www.cnblogs.com/yangruomao/p/18187294

相关文章

  • [附源码]新天龙八部3永恒经典之江山策仿官方_联网+单机搭建架设教程
    新天龙八部3永恒经典之江山策仿官_联网架设搭建_附赠GM工具+视频教程本教程仅限学习使用,禁止商用,一切后果与本人无关,此声明具有法律效应!!!!教程是本人亲自搭建成功的,绝对是完整可运行的,踩过的坑都给你们填上了。如果你是小白也没问题,跟着教程走也是可以搭建成功的,但是一定要有耐心......
  • __asm__ __volatile__ GCC的内嵌汇编语法 AT&T汇编语言语法 AT&T ASM Syntax
    1Overview 开发一个OS,尽管绝大部分代码只需要用C/C++等高级语言就可以了,但至少和硬件相关部分的代码需要使用汇编语言,另外,由于启动部分的代码有大小限制,使用精练的汇编可以缩小目标代码的Size。另外,对于某些需要被经常调用的代码,使用汇编来写可以提高性能。所以我们必须了解汇......
  • JVM类加载器ClassLoader源码剖析
    在JVM类加载器分类中通过ClassLoader获取了不同类型的类加载器,它是如此之重要那么ClassLoader究竟为何物呢?通过源码分析(以jdk17示例):调试跟踪ClassLoader:......
  • qgroundcontrol开发环境搭建源码编译
    qgroundcontrol是一款无人机地面站开源软件,C++/QT开发在https://github.com/mavlink/qgroundcontrol上就能找到,选择稳定版下载最新的是2.6下载https://github.com/mavlink/qgroundcontrol/archive/Stable_V2.6.zipQT的对应版本http://download.qt-project.org/official_releas......
  • [附源码+文档]Java Swing小游戏源码合集(14款)_毕业设计必选项目
    (小众游戏塔防迷宫动作剧情类等)16款游戏源码Javaswing五子棋联网版源代码Javaswing贪吃蛇游戏开发教程+源码Javaswing超级玛丽游戏Javaswing俄罗斯方块项目源码Javaswing飞机大战游戏源码Javaswing雷电游戏源码Javaswing连连看游戏源码Javaswing模拟写字板源码......
  • [附源码]石器时代_恐龙宝贝内购版_三网H5手游_附GM
    石器时代之恐龙宝贝内购版_三网H5经典怀旧Q萌全网通手游_Linux服务端源码_视频架设教程_GM多功能授权后台_CDK授权后台本教程仅限学习使用,禁止商用,一切后果与本人无关,此声明具有法律效应!!!!教程是本人亲自搭建成功的,绝对是完整可运行的,踩过的坑都给你们填上了如果你是小白也没问题......
  • UcOs-III 源码阅读: os_tmr.c
    对定时器源文件os_tmr.c进行源码阅读与注释://功能:创建、删除、启动、停止、删除、初始化模块、获取定时器剩余时间、获取定时器状态、//创建定时器API:OS_TmrCreate//删除定时器API:OS_TmrDel//启动定时器API:O......
  • JDK源码阅读-------自学笔记(二十六)(java.util.Map 自定义讲解)
    一、简介Map就是用来存储“键(key)-值(value)”对的.通过键寻找value,所以键不能重复.数组的本质也是一种键值对,区别就是索引一般是数字,而Map的Key可以是任意对象(字符串,数字),相当于把数组的索引范围扩的更大,使用更方便.实际开发中较为常用.二、Map的常用方法实例(1......
  • windows下源码编译CMake项目
    Cmake项目1、安装路径和源码安装包下载地址:https://cmake.org/download/源码地址https://github.com/Kitware/CMake2、编译源码下载后会有一个CMake-master的文件夹在里面新建一个build目录打开cmake-gui可执行文件出现cmake的界面,设置source路径为刚刚的CMake-......
  • iceoryx源码阅读(八)——IPC通信机制
    目录1 整体结构2 序列化与反序列化3 类Unix系统的实现3.1 发送函数send3.2 接收函数receive4 Windows系统的实现4.1 发送函数send4.2 接收函数receive5 Roudi的监听逻辑1 整体结构通过前面的介绍,订阅者、发布者与Roudi守护进程之间也需要通信,如上文介绍的,请求Roudi守护进村创建......