每日一面系列之volatile 的理解

2023-11-13

volatile 是 Java 虚拟机提供的轻量级的同步机制,有三大特点:保证可见性;不保证原子性;禁止指令重排

保证可见性

当多个线程操作共享数据时,彼此是不可见的。由此提出 JMM (java 内存模型)

JMM (java 内存模型) :是一种抽象的概念,并不真实存在,它描述的一组规则或者规范。通过这些规则、规范定义了程序中各个变量的访问方式。

在每个线程创建时,JVM 都会为其创建一个工作内存,工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问。但线程对变量的操作(读取、赋值)必须在工作内存中进行,首先将变量从主内存拷贝到自己的工作内存,然后对变量进行操作,操作完成后再将变量更新到主内存,也就是说,每个线程操作的实际上是变量的副本,他们只操作了自己复制的那一份,别的线程如何操作的不知道。

加上 volatile修饰之后,会强制将修改的值立即写入主内存。注意的是 volatile也不能用太多,会导致总线风暴

不保证原子性

public class JucTest {
​
    public static void main(String[] args) {
        AddData addData = new AddData();
        for (int i = 1; i <= 20; i++) {
            new Thread(() -> {
                for (int j = 1; j <= 1000; j++) {
                    addData.add();
                }
            }, String.valueOf(i)).start();
        }
​
        while (Thread.activeCount() > 2){
            Thread.yield();
        }
​
        System.out.println("num = " + addData.num);
    }
}
​
class AddData{
    public volatile int num = 0;
​
    public void add(){
        num++;
    }
}

输出结果为:

 

ini

复制代码

num = 17886

循环了20000次,为什么结果却不是20000呢?

因为 num++不是一个原子操作,在多线程下是非线程安全的。

理想的情况是:

线程1在自己的工作内存中,将num改为1,写回主内存,由于内存可见性,通知线程2 num 已经改为1,线程2将num复制到自己的工作内存,将num++,改为2,写回主内存,通知线程3,以此类推。

但是在多线程的环境下,竞争调度,线程1刚刚要写入1的时候线程被挂起,2号线程将1 写入主内存,此时应该通知其他线程,主内存的值已经改为了1 了,由于线程操作极快,还未来及通知其他线程,刚才挂起的线程1将 num = 1 又写入了主内存,主内存的值被覆盖,出现了丢失写值

image.png

禁止指令重排

DCL 双重校验锁

public class Singleton { ​  
  
private volatile static Singleton instance; ​   
 public static Singleton getInstance() {       
     if (instance == null) {            
         synchronized (Singleton.class) {  
              if (instance == null) {  
                  System.out.println("实例化 Singleton");                   
               instance = new Singleton();               
            }           
      }       
   }        
  return instance;    
 } ​ 
}

instance = new Singleton() 并不是一个原子操作,而是分为三步

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

由于步骤2和步骤3不存在依赖关系,操作系统会进行指令重排序。也就是说步骤3可能会先执行

memory=allocate();        // 1.分配对象内存空间
instance=memory;        // 3.设置instance指向刚分配的内存地址,此时instance!=null,但是对象还没有初始化完成!
instance(memory);        // 2.初始化对象

通过步骤3已经 != null 了,但是此时还没初始化完成,所以上面的 第二个 if (instance == null)要加,防止进来多个线程,实例化多次。

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

每日一面系列之volatile 的理解 的相关文章

随机推荐

  • MTK手机平台充电原理

    EPT GPIO初始化文件 bsp gpio ept config c 1 知识点总结 1 1 Official 参考充电电路 Figure 1 1 参考电路 VCHG USB正极 VCDT VCHG Charger Detect充电电压检
  • SpringBoot实现基础的sso单点登录

    前言 本文借鉴学习了团团大佬的文章和源码 原文地址 想知道单点登录原理等理论可以移步大佬文章 代码及实现 本次学习基于session模拟了数据库查询和token权限认证 构建 准备三个服务器 这里用三个springboot项目直接模拟了 他
  • python把坐标点画成三维图形_python matplotlib模块——绘制三维图形、三维数据散点图(转)...

    python matplotlib模块 是扩展的MATLAB的一个绘图工具库 他可以绘制各种图形 可是最近最的一个小程序 得到一些三维的数据点图 就学习了下python中的matplotlib模块 如何绘制三维图形 初学者 可能对这些第三方
  • 操作系统考试复习——第四章 4.3连续分配存储管理方式

    在这里的开头需要讲述一下碎片 碎片分为内碎片和外碎片两种 内碎片 分区之内未被利用的空间外碎片 分区之间难以利用的空闲分区 通常是小空闲分区 连续分配存储管理方式 为了能将用户程序装入内存 必须为它分配一定大小的内存空间 1 单一连续分配
  • android移动开发软件安装,android studio 开发的安卓软件怎么安装到手机上

    android studio 开发中如何让运用运行在真机中 大体实现方式有3种 分别是 真机通过usb插到电脑上进行开发 android studio 上面编写程序进行编译apk版本 通过其他助手或者adb命令推送到手机中运行 通过wifi
  • docker入门级使用教程

    1 Docker是什么 简单理解为主要应用在Linux上的虚拟机 后台中常用到 镜像 系统的镜像文件 是一个系统 容器 正在运行中的虚拟机 tar文件 将镜像直接保存为tar文件 是一个可加载的中间文件 Dockfile 配置文件 根据其中
  • FFT简介小结

    FFT是离散傅立叶变换的快速算法 可以将一个信号变换到频域 有些信号在时域上是很难看出什么特征的 但是如果变换到频域之后 就很容易看出特征了 这就是很多信号分析采用FFT变换的原因 另外 FFT可以将一个信号的频谱提取出来 这在频谱分析方面
  • 【微信小程序】wx.onBluetoothDeviceFound 安卓机第一次可以连接蓝牙设备,第二次搜索不到问题

    问题原因 wx onBluetoothDeviceFound 接口返回的是新的蓝牙设备 之前连接过的在部分安卓机型上 不算做新的蓝牙设备 故重新连接搜索不到 解决办法 操作完成后要及时关闭连接 同时也要关闭蓝牙设备 否则安卓下再次进入会搜索
  • 使用遗传算法解决单目标优化问题及MATLAB代码实现

    使用遗传算法解决单目标优化问题及MATLAB代码实现 随着现代计算机科学的快速发展 越来越多的实际问题需要使用最优化算法进行求解 其中 遗传算法因其应用范围广泛 求解能力强等特点而备受关注和研究 本文将介绍如何使用遗传算法求解单目标优化问题
  • Ubuntu 18.04.6 Android Studio Giraffe adb logcat 无法使用

    在 Ubuntu 18 04 6 上 在链接上设备以后 发现可以用 Android Studio 安装应用 但无法用 Android Studio 看 logcat 手动从命令行停止 启动 adb 会报错如下 daemon not runn
  • 自定义导航栏

  • cenos7 下通过yum安装和配置mariadb

    centos 7 的软件源中有mariadb的 所以这里我们不配置他的源了 若没有的话可以用以下方式来配软件源 vi etc yum repos d MariaDB repo 插入以下内容 MariaDB 10 2 4 CentOS rep
  • 查看云服务器信息,查看云服务器信息

    查看云服务器信息 内容精选 换一换 对于已安装InfiniBand网卡驱动的H2型弹性云服务器 以下简称IB云服务器 可以通过如下方式 检查云服务器的IB网卡驱动安装成功 网络连通 可以正常工作 检查过程中 如果发现您的弹性云服务器未安装i
  • echarts图表鼠标悬停时 图例错位

    1 问题 当页面body拥有zoom属性之后 鼠标划过echarts图表时 触发位置就不正常 2 原因分析 这都是因为设置了zoom 如果你在你的项目中设置了zoom以达到缩放比例的适配 在使用echarts图表时就会出现错位的问题 3 解
  • cmake(二十八)Cmake工具链

    一 选择编译器及设置编译器选项 1 应用场景 1 在 实际的项目平台中 可能安装有 多个版本 的 编译器 2 同时由于 不同的功能 可能会需要设置 不同的编译参数 2 初始状态 3 IDE工具CLion配置 4 CMAKE C COMPIL
  • pycharm查看全部tensor数据,取消省略

    方法一
  • 【深度学习——点云】DGCNN(EdgeConv)

    这篇文章提出一种边卷积 EdgeConv 操作 来完成点云中点与点之间关系的建模 使得网络能够更好地学习局部和全局特征 论文地址 Dynamic Graph CNN For Learning On Point Clouds 1 Motiva
  • rustup 慢_Rust 慢更贴[持续更新Rust学习的点点滴滴]

    入门篇 Rust入门系列 这个帖子会一直更新 欢迎大家回复 首先从安装来写把 rust安装 在写这篇文章的时候 rust最新版本是1 35 安装的步骤大家可以直接上rust官网 https www rust lang org curl ht
  • 招银网络科技电话面试

    1 关于项目的负责内容 还是非常有必要熟悉应急 天基的基础传输模块的 基本面试中都会觉得只界面模块很单薄 应急 基础传输模块 无人机网络协议 速率控制模块 界面模块 天基 基础传输模块 MRUDP 界面模块 2 TCP长连接 问 如何在TC
  • 每日一面系列之volatile 的理解

    volatile 是 Java 虚拟机提供的轻量级的同步机制 有三大特点 保证可见性 不保证原子性 禁止指令重排 保证可见性 当多个线程操作共享数据时 彼此是不可见的 由此提出 JMM java 内存模型 JMM java 内存模型 是一种