Java内存模型 volatile 线程安全

专栏系列文章地址:https://blog.csdn.net/qq_26437925/article/details/145290162

本文目标:

  1. 认识JMM
  2. 认识volatile关键字:可见性和顺序性
  3. 理解线程安全的概念

目录

    • 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() {@Overridepublic 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 看到了原子性可见性顺序性三个重要性质,这构成了多线程线程安全编程的基础。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Proteus仿真】【51单片机】多功能计算器系统设计

目录 一、主要功能 二、使用步骤 三、硬件资源 四、软件设计 五、实验现象 联系作者 一、主要功能 1、LCD1602液晶显示 2、矩阵按键​ 3、加减乘除&#xff0c;开方运算 4、带符号运算 5、最大 999*999 二、使用步骤 基于51单片机多功能计算器 包含&#xff1a;程序&…

three.js+WebGL踩坑经验合集(4.1):THREE.Line2的射线检测问题(注意本篇说的是Line2,同样也不是阈值方面的问题)

上篇大家消化得如何了&#xff1f; 笔者说过&#xff0c;1级编号不同的两篇博文相对独立&#xff0c;所以这里笔者还是先给出完整代码&#xff0c;哪怕跟&#xff08;3&#xff09;没有太大区别。 这里我们把线的粗细调成5&#xff08;排除难选中的因素&#xff09;&#xff…

SAP内向交货单详解

【SAP系统研究】 #SAP #交货单 #内向交货单 一、内向交货单的概念 内向交货单,Inbound Delivery,是SAP系统中用于管理外部供应商或内部工厂向公司发货的文档。它记录了货物从供应商到公司仓库或生产地点的运输和接收过程。 内向交货单的主要功能有: (1)货物接收:用于…

扩展无限可能:Obsidian Web Viewer插件解析

随着 Obsidian 1.8.3 正式版的发布&#xff0c;备受期待的官方核心插件——Web Viewer 也终于上线。本文将从插件启用、设置以及应用场景三个方面详细介绍如何使用这一新功能&#xff0c;和大家一起更好地利用 Obsidian 进行内容管理和知识整理。 插件启用 Web Viewer作为官方…

如何在 ACP 中建模复合罐

概括 本篇博文介绍了 ANSYS Composite PrepPost (ACP) 缠绕向导。此工具允许仅使用几个条目自动定义高压罐中常见的悬垂复合结构。 ACP 绕线向导 将必要的信息输入到绕组向导中。重要的是要注意“参考半径”&#xff0c;它代表圆柱截面的半径&#xff0c;以及“轴向”&#x…

本地搭建deepseek-r1

一、下载ollama(官网下载比较慢&#xff0c;可以找个网盘资源下) 二、安装ollama 三、打开cmd&#xff0c;拉取模型deepseek-r1:14b(根据显存大小选择模型大小&#xff09; ollama pull deepseek-r1:14b 四、运行模型 ollama run deepseek-r1:14b 五、使用网页api访问&#x…

OpenCV:闭运算

目录 1. 简述 2. 用膨胀和腐蚀实现闭运算 2.1 代码示例 2.2 运行结果 3. 闭运算接口 3.1 参数详解 3.2 代码示例 3.3 运行结果 4. 闭运算的应用场景 5. 注意事项 相关阅读 OpenCV&#xff1a;图像的腐蚀与膨胀-CSDN博客 OpenCV&#xff1a;开运算-CSDN博客 1. 简述…

Docker技术简介

Docker容器技术 一、认识Docker容器技术和镜像仓库 容器 容器是一种轻量级、可移植、自包含的软件运行环境,允许多个应用程序在同一个操作系统实例上独立运行,而不会互相干扰。想必大家有点不好理解,那么 利用通俗易懂的方式概述容器: 想象一下,你有一个应用程序,但是这…

Windows程序设计9:文件的读写操作

文章目录 前言一、文件的写操作WriteFile1.WriteFile介绍2.WriteFile实例二、文件的读操作ReadFile1.ReadFile简介2.ReadFile实例总结前言 Windows程序设计9:文件的读写操作。 一、文件的写操作WriteFile 1.WriteFile介绍 从文件指针指向的位置开始,将数据写入到一个文件中…

AJAX笔记入门篇

黑马程序员视频地址&#xff1a; 黑马程序员前端AJAX入门到实战全套教程https://www.bilibili.com/video/BV1MN411y7pw?vd_source0a2d366696f87e241adc64419bf12cab&spm_id_from333.788.videopod.episodes&p2https://www.bilibili.com/video/BV1MN411y7pw?vd_source…

洛谷 P10288 [GESP样题 八级] 区间 C++ 完整题解(STL二分法)

本文并非最优解&#xff0c;但能通过。 一、题目链接 P10288 [GESP样题 八级] 区间 - 洛谷 二、解题思路 **本题的大意就是求出a[l~r]间x出现的次数&#xff0c;由于数据较大、较多&#xff0c;所以暴力容易超时。** 首先新建一个mp&#xff0c;mp[x]是一个vector&#xff0c;里…

springboot集成钉钉,发送钉钉日报

目录 1.说明 2.示例 3.总结 1.说明 学习地图 - 钉钉开放平台 在钉钉开放文档中可以查看有关日志相关的api&#xff0c;主要用到以下几个api&#xff1a; ①获取模板详情 ②获取用户发送日志的概要信息 ③获取日志接收人员列表 ④创建日志 发送日志时需要根据模板规定日志…

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

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

学习串行通信

本文来源&#xff1a; [8-1] 串口通信_哔哩哔哩_bilibili 智谱清言 ------------ 串口&#xff08;Serial Port&#xff09;&#xff1a; 串口是一种应用非常广泛的通讯接口&#xff0c;串口成本低&#xff0c;容易使用&#xff0c;通信线路简单&#xff0c;可实现两个设…

【memgpt】letta 课程4:基于latta框架构建MemGpt代理并与之交互

Lab 3: Building Agents with memory 基于latta框架构建MemGpt代理并与之交互理解代理状态,例如作为系统提示符、工具和agent的内存查看和编辑代理存档内存MemGPT 代理是有状态的 agents的设计思路 每个步骤都要定义代理行为 Letta agents persist information over time and…

(二)QT——按钮小程序

目录 前言 按钮小程序 1、步骤 2、代码示例 3、多个按钮 ①信号与槽的一对一 ②多对一&#xff08;多个信号连接到同一个槽&#xff09; ③一对多&#xff08;一个信号连接到多个槽&#xff09; 结论 前言 按钮小程序 Qt 按钮程序通常包含 三个核心文件&#xff1a; m…

SpringCloudGateWay和Sentinel结合做黑白名单来源控制

假设我们的分布式项目&#xff0c;admin是8087&#xff0c;gateway是8088&#xff0c;consumer是8086 我们一般的思路是我们的请求必须经过我们的网关8088然后网关转发到我们的分布式项目&#xff0c;那我要是没有处理我们绕过网关直接访问项目8087和8086不也是可以&#xff1…

玩转大语言模型——配置图数据库Neo4j(含apoc插件)并导入GraphRAG生成的知识图谱

系列文章目录 玩转大语言模型——使用langchain和Ollama本地部署大语言模型 玩转大语言模型——ollama导入huggingface下载的模型 玩转大语言模型——langchain调用ollama视觉多模态语言模型 玩转大语言模型——使用GraphRAGOllama构建知识图谱 玩转大语言模型——完美解决Gra…

C# Winform制作一个登录系统

using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace 登录 {p…