Java内存模型 volatile 线程安全

目录

    • Java内存模型
      • 可见性例子和volatile
      • volatile如何保证可见性
      • 原子性与单例模式
      • i++非原子性
    • 线程安全

Java内存模型

参考学习: Java Memory Model外文文档

  • CPU与内存,可参考:https://blog.csdn.net/qq_26437925/article/details/145303267
    在这里插入图片描述

  • Java线程与内存
    在这里插入图片描述

  • 主内存:java虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生;为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。

  • 工作内存:java虚拟机中每个线程都有自己的工作内存,该内存是线程私有的;为了方便理解,可以认为是虚拟机栈。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,则必须通过主内存来作为中介进行传递。

主内存与Java工作内存之间的具体交互协议,虚拟机保证如下的每一种操作都是原子的,不可再分的(对于double,long类型的变量有例外,商用JVM基本优化了这个问题)

  • lock: 作用于主内存的变量,它把一个变量标识为一个线程独占的状态

  • unlock:作用于主内存的变量, 解锁

  • load:作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中

  • use:作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作

  • assign: 作用于工作内存的变量,它把一个从执行引擎接收到的赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作

  • store: 作用于工作内存的变量,它把工作内存中的一个变量的值传送到主内存中,以便随后的write操作使用

  • write:作用于主内存的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

可见性例子和volatile

public class Main {

    private static volatile Boolean flag = true;

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

       Thread thread =  new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A start");
                while (flag) {

                }
                System.out.println("A end");
            }
        });
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (Exception e) {
        }
        flag = false;

        System.out.println("main end");

    }
}

输出如下:
在这里插入图片描述

主线程后执行,设置了flag=false,由于volatile的作用,导致线程可见flag,所以线程A可以结束。

volatile如何保证可见性

硬件层两个内存屏障:load barrier、store barrier;其有两个功能:

  • 禁止屏障前后的指令重排序
  • 强制把写缓冲区的的数据写入主内存
屏障类型指令示例说明
LoadLoad BarriersLoad1;LoadLoad;Load2该屏障确保Load1数据的装载先于Load2及其后所有装载指令的的操作
StoreStore BarriersStore1;StoreStore;Store2该屏障确保Store1立刻刷新数据到内存(使其对其他处理器可见)的操作先于Store2及其后所有存储指令的操作
LoadStore BarriersLoad1;LoadStore;Store2确保Load1的数据装载先于Store2及其后所有的存储指令刷新数据到内存的操作
StoreLoad BarriersStore1;StoreLoad;Load2该屏障确保Store1立刻刷新数据到内存的操作先于Load2及其后所有装载装载指令的操作。它会使该屏障之前的所有内存访问指令(存储指令和访问指令)完成之后,才执行该屏障之后的内存访问指令

其中StoreLoad Barriers同时具备其他三个屏障的效果,因此也称之为全能屏障(mfence),是目前大多数处理器所支持的;但是相对其它屏障,该屏障的开销相对昂贵。

volatile 正是通过加入内存屏障,禁止指令重排优化来实现可见性和有序性,即

  1. 每个volatile写操作的前面插入一个StoreStore屏障;
  2. 在每个volatile写操作的后面插入一个StoreLoad屏障(全能屏障);
  3. 在每个volatile读操作的前面插入一个LoadLoad屏障;
  4. 在每个volatile读操作的后面插入一个LoadStore屏障。

所以线程写volatile变量的过程:

  1. 改变线程工作内存的中volatile变量副本的值。
  2. 将改变后的副本的值从工作内存刷新到主内存。

线程读volatile变量的过程:

  1. 从主内存中读取volatile变量的最新值到线程的工作内存中。
  2. 从工作内存中读取volatile变量的副本。

如上例子中,当主线程写flag时,会将数据刷新到主内存中;而线程thread读取的时候,也是确保读取到的是主内存数据,所有能够实现例子代码中的可见性验证。

原子性与单例模式

class Singleton{
    private byte[] data = new byte[1024];

    private static Singleton instance = null;

    public static Singleton getInstance(){
        if (null == instance) {
            synchronized (Singleton.class) {
                System.out.println("new Singleton");
                instance = new Singleton();
            }
        }
        return instance;
    }

}

public class Main {


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

        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                Singleton singleton = Singleton.getInstance();
            }).start();
        }
    }
}

多次运行,可以看到有输出如下的例子:
在这里插入图片描述

不是double check的单例模式,实际上会new出多个实例,无法实现单例模式。

因为Object o = new Object();的汇编指令如下,不是一个原子操作

0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init>>
7 astore_1
8 return

一个对象创建的过程:(记住3步)

  1. 堆内存中申请了一块内存(new指令)【半初始化状态,成员变量初始化为默认值】
  2. 这块内存的构造方法执行(invokespecial指令)
  3. 栈中变量建立连接到这块内存(astore_1指令)

i++非原子性

import java.util.Random;
import java.util.concurrent.TimeUnit;

public class Main {

    public volatile static int num = 0;

    public static void add() {
        num++;
    }

    public synchronized static void addSync() {
        num++;
    }

    private final static int N = 30;

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[N];
        for(int i=0;i<N;i++){
            threads[i] = new Thread(()->{
                try{
                    TimeUnit.MILLISECONDS.sleep(new Random().nextInt(10));
                    int addCnt = 100;
                    for(int j=0;j<addCnt;j++){
                       add();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
        for(int i=0;i<N;i++) {
            threads[i].join();
        }
        System.out.println("num:" + num);
    }

}
/* output
小于3000的值
*/

线程安全

  • 什么是线程安全问题?

当多个线程共享同一个全局变量,做写的时候,可能会受到其它线程的干扰,导致数据有问题,这中现象叫做线程安全问题

关键词:共享数据,多线程,并发写操作

结合本文和上一篇:https://blog.csdn.net/qq_26437925/article/details/145303267 看到了原子性可见性顺序性三个重要性质,这构成了多线程线程安全编程的基础。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/961985.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【FreeRTOS 教程 三】协程状态、优先级、实现及调度

目录 一、协程介绍&#xff1a; &#xff08;1&#xff09;协程的特点&#xff1a; &#xff08;2&#xff09;协程的优势&#xff1a; 二、协程状态&#xff1a; &#xff08;1&#xff09;协程状态说明&#xff1a; &#xff08;2&#xff09;协程状态图示&#xff1a;…

堆的存储(了解)

由于堆是⼀个完全⼆叉树&#xff0c;因此可以⽤⼀个数组来存储。&#xff08;如果不清楚大家可以回顾⼆叉树的存储&#xff08;上&#xff09;c文章里的顺序存储&#xff09; 结点下标为 i &#xff1a; 如果⽗存在&#xff0c;⽗下标为 i/2 &#xff1b; 如果左孩⼦存在&…

谭浩强C语言程序设计(3) 7章

1、递归实现N的阶乘 c复制 #include <cstdio> // 包含标准输入输出库// 计算n的阶乘 int total 0; // 定义全局变量total用于存储阶乘结果// 递归函数计算阶乘 int fac(int a){// 如果输入的数小于0&#xff0c;输出错误信息if (a < 0){printf("%d < 0,err…

WPF基础 | WPF 常用控件实战:Button、TextBox 等的基础应用

WPF基础 | WPF 常用控件实战&#xff1a;Button、TextBox 等的基础应用 一、前言二、Button 控件基础2.1 Button 的基本定义与显示2.2 按钮样式设置2.3 按钮大小与布局 三、Button 的交互功能3.1 点击事件处理3.2 鼠标悬停与离开效果3.3 按钮禁用与启用 四、TextBox 控件基础4.…

MATLAB的数据类型和各类数据类型转化示例

一、MATLAB的数据类型 在MATLAB中 &#xff0c;数据类型是非常重要的概念&#xff0c;因为它们决定了如何存储和操作数据。MATLAB支持数值型、字符型、字符串型、逻辑型、结构体、单元数组、数组和矩阵等多种数据类型。MATLAB 是一种动态类型语言&#xff0c;这意味着变量的数…

模型I/O

文章目录 什么是模型I/O模型I/O功能之输出解析器输出解析器的功能输出解析器的使用Pydantic JSON输出解析器结构化输出解析器 什么是模型I/O 模型I/O在所有LLM应用中&#xff0c;核心元素无疑都是模型本身。与模型进行有效的交互是实现高效、灵活和可扩展应用的关键。LangChain…

docker安装Redis:docker离线安装Redis、docker在线安装Redis、Redis镜像下载、Redis配置、Redis命令

一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull redis:7.4.0 2、离线包下载 两种方式&#xff1a; 方式一&#xff1a; -&#xff09;在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -&#xff09;导出 # 导出镜像…

QT串口通信,实现单个温湿度传感器数据的采集

1、硬件设备 RS485中继器(一进二出),usb转485模块、电源等等 => 累计115元左右。 2、核心代码 #include "MainWindow.h" #include "ui_MainWindow.h"MainWindow::

android主题设置为..DarkActionBar.Bridge时自定义DatePicker选中日期颜色

安卓自定义DatePicker选中日期颜色 背景&#xff1a;解决方案&#xff1a;方案一&#xff1a;方案二&#xff1a;实践效果&#xff1a; 背景&#xff1a; 最近在尝试用原生安卓实现仿element-ui表单校验功能&#xff0c;其中的的选择日期涉及到安卓DatePicker组件的使用&#…

6.工厂模式(Factory Method)

定义 通过“对象创建” 模式绕开new&#xff0c;来避免对象创建&#xff08;new&#xff09;过程中所导致的紧耦合&#xff08;依赖具体类&#xff09;&#xff0c;从而支持对象创建的稳定。它是接口抽象之后的第一步工作。 动机 在软件系统中&#xff0c;经常面临着创建对象…

Java CAS操作

通过前面的学习认识到了CPU缓存&#xff0c;Java内存模型&#xff0c;以及线程安全的原子、可见、顺序三大特性。本文则重点认识CAS操作&#xff0c;这是Java并发编程常见的一个操作&#xff0c;AbstractQueuedSynchronizer基于此操作提供了丰富的同步器和各种锁。 CAS&#x…

【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.25 视觉风暴:NumPy驱动数据可视化

1.25 视觉风暴&#xff1a;NumPy驱动数据可视化 目录 #mermaid-svg-i3nKPm64ZuQ9UcNI {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-i3nKPm64ZuQ9UcNI .error-icon{fill:#552222;}#mermaid-svg-i3nKPm64ZuQ9UcNI …

鸟瞰欧洲(意境欧洲) 第一季

目录 《鸟瞰欧洲 第一季》纪录片笔记一、基本信息二、详细内容&#xff08;一&#xff09;剧集设置&#xff08;二&#xff09;各国亮点1. **荷兰**2. **意大利**3. **德国**4. **英国**5. **西班牙**6. **波兰** &#xff08;三&#xff09;拍摄特色 三、特色与评价四、总结五…

【MQ】探索 Kafka

高性能 消息的顺序性、顺序写磁盘 零拷贝 RocketMQ内部主要是使用基于mmap实现的零拷贝&#xff0c;用来读写文件 减少cpu的拷贝次数和上下文切换次数&#xff0c;实现文件的高效读写操作 Kafka 零拷贝 Kafka 使用到了 mmap 和 sendfile 的方式来实现零拷贝。分别对应 Jav…

供应链系统设计-供应链中台系统设计(十一)- 清结算中心概念片篇

概述 上篇供应链系统设计-供应链中台系统设计&#xff08;十&#xff09;- 清结算中心概念片篇文中提到了什么是金融客户、资金账号、资金账户、以及资金账号和资金账户的关系&#xff0c;如下图所示&#xff1a; 这些对于清算和结算来说都是前置的概念&#xff0c;本篇文章我…

allegro修改封闭图形线宽

说在前面 我们先把最优解说在前面,然后后面再说如果当时不熟悉软件的时候为了挖孔是用了shapes该怎么修改回来。 挖空最方便的方式是在cutout层画一个圆弧,下面开始图解,先add一个圆弧 z 最好是在画的时候就选择好层,如果忘记了后续再换回去也行,但好像软件有bug,此处并…

使用scikit-learn中的KNN包实现对鸢尾花数据集的预测

引言 K最近邻&#xff08;KNN&#xff09;算法是一种简单且直观的分类算法。它通过计算数据点之间的距离来对新样本进行分类。鸢尾花数据集是一个经典的机器学习数据集&#xff0c;包含了三种不同类型的鸢尾花&#xff0c;每种类型由四个特征&#xff08;花萼长度、花萼宽度、…

Hive:静态分区(分区语法,多级分区,分区的查看修改增加删除)

hive在建表时引入了partition概念。即在建表时&#xff0c;将整个表存储在不同的子目录中&#xff0c;每一个子目录对应一个分区。在查询时&#xff0c;我们就可以指定分区查询&#xff0c;避免了hive做全表扫描&#xff0c;从而提高查询率。 oracle和Hive分区的区别 orcale在…

基于FPGA的BT656解码

概述 BT656全称为“ITU-R BT.656-4”或简称“BT656”,是一种用于数字视频传输的接口标准。它规定了数字视频信号的编码方式、传输格式以及接口电气特性。在物理层面上,BT656接口通常包含10根线(在某些应用中可能略有不同,但标准配置为10根)。这些线分别用于传输视频数据、…

随机矩阵投影长度保持引理及其证明

原论文中的引理 2 \textbf{2} 2 引理 2 \textbf{2} 2的内容​​ &#x1f449;前提 1 1 1&#xff1a;设一个随机矩阵 S ( s i j ) ∈ R t d S\text{}(s_{ij})\text{∈}\mathbb{R}^{t\text{}d} S(sij​)∈Rtd&#xff0c;每个元素 s i j s_{ij} sij​独立同分布于 N ( 0 , …