java高并发系列之JMM相关的一些概念

2023-11-11

JMM(java内存模型),由于并发程序要比串行程序复杂很多,其中一个重要原因是并发程序中数据访问一致性安全性将会受到严重挑战。如何保证一个线程可以看到正确的数据呢?这个问题看起来很白痴。对于串行程序来说,根本就是小菜一碟,如果你读取一个变量,这个变量的值是1,那么你读取到的一定是1,就是这么简单的问题在并行程序中居然变得复杂起来。事实上,如果不加控制地任由线程胡乱并行,即使原本是1的数值,你也可能读到2。因此我们需要在深入了解并行机制的前提下,再定义一种规则,保证多个线程间可以有小弟,正确地协同工作。而JMM也就是为此而生的。

JMM关键技术点都是围绕着多线程的原子性、可见性、有序性来建立的。我们需要先了解这些概念。

原子性

原子性是指操作是不可分的,要么全部一起执行,要么不执行。在java中,其表现在对于共享变量的某些操作,是不可分的,必须连续的完成。比如a++,对于共享变量a的操作,实际上会执行3个步骤:

1.读取变量a的值,假如a=1

2.a的值+1,为2 

3.将2值赋值给变量a,此时a的值应该为2

这三个操作中任意一个操作,a的值如果被其他线程篡改了,那么都会出现我们不希望出现的结果。所以必须保证这3个操作是原子性的,在操作a++的过程中,其他线程不会改变a的值,如果在上面的过程中出现其他线程修改了a的值,在满足原子性的原则下,上面的操作应该失败。

java中实现原子操作的方法大致有2种:锁机制无锁CAS机制,后面的章节中会有介绍。

可见性

可见性是值一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的。有些同学会说修改同一个变量,那肯定是可以看到的,难道线程眼盲了?

为什么会出现这种问题呢?

看一下java线程内存模型:

  • 我们定义的所有变量都储存在 主内存

  • 每个线程都有自己 独立的工作内存,里面保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)

  • 线程对共享变量所有的操作都必须在自己的工作内存中进行,不能直接从主内存中读写(不能越级)

  • 不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行。(同级不能相互访问)

线程需要修改一个共享变量X,需要先把X从主内存复制一份到线程的工作内存,在自己的工作内存中修改完毕之后,再从工作内存中回写到主内存。如果线程对变量的操作没有刷写回主内存的话,仅仅改变了自己的工作内存的变量的副本,那么对于其他线程来说是不可见的。而如果另一个变量没有读取主内存中的新的值,而是使用旧的值的话,同样的也可以列为不可见。

共享变量可见性的实现原理:

线程A对共享变量的修改要被线程B及时看到的话,需要进过以下步骤:

1.线程A在自己的工作内存中修改变量之后,需要将变量的值刷新到主内存中 2.线程B要把主内存中变量的值更新到工作内存中

关于线程可见性的控制,可以使用volatilesynchronized来实现,后面章节会有详细介绍。

有序性

有序性指的是程序按照代码的先后顺序执行。

为了性能优化,编译器和处理器会进行指令冲排序,有时候会改变程序语句的先后顺序,比如程序。

 
  1. int a = 1; //1

  2. int b = 20; //2

  3. int c = a + b; //3

编译器优化后可能变成

 
  1. int b = 20; //1

  2. int a = 1; //2

  3. int c = a + b; //3

上面这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。

在单例模式的实现上有一种双重检验锁定的方式,代码如下:

public class Singleton {

static Singleton instance;

static Singleton getInstance(){

if (instance == null) {

synchronized(Singleton.class) {

if (instance == null)

instance = new Singleton();

}

}

return instance;

}

}

我们先看 instance=newSingleton();

未被编译器优化的操作:

  1. 指令1:分配一款内存M

  2. 指令2:在内存M上初始化Singleton对象

  3. 指令3:将M的地址赋值给instance变量

编译器优化后的操作指令:

  1. 指令1:分配一块内存S

  2. 指令2:将M的地址赋值给instance变量

  3. 指令3:在内存M上初始化Singleton对象

现在有2个线程,刚好执行的代码被编译器优化过,过程如下:

最终线程B获取的instance是没有初始化的,此时去使用instance可能会产生一些意想不到的错误。

现在比较好的做法就是采用静态内部内的方式实现:

 
public class SingletonDemo {

private SingletonDemo() {

}

private static class SingletonDemoHandler{

private static SingletonDemo instance = new SingletonDemo();

}

public static SingletonDemo getInstance() {

return SingletonDemoHandler.instance;

}

}

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

java高并发系列之JMM相关的一些概念 的相关文章

  • Java - 因内存不足错误而关闭

    关于如何最好地处理这个问题 我听到了非常矛盾的事情 并且陷入了以下困境 OOME 会导致一个线程崩溃 但不会导致整个应用程序崩溃 我需要关闭整个应用程序 但不能 因为线程没有剩余内存 我一直认为最佳实践是让它们离开 这样 JVM 就会死掉
  • (Java) App Engine 中的静态文件无法访问

    The 示例文档 http code google com appengine docs java gettingstarted staticfiles html表示您只需将文件放在 war 或子目录 中 并且应该可以从主机访问它们 只要它
  • 将SQL数据引入jquery availabletag

    我正在尝试制作自动完成文本框 但如何将 SQL 数据包含到 jquery 可用标记并循环它 我无法根据以下代码执行该功能 任何帮助 将不胜感激 谢谢 这是我的预期输出 预期结果演示 http jsfiddle net VvETA 71 jq
  • FileNotFoundException - Struts2 文件上传

    Strange FileNotFoundException使用Struts2上传文件时 这是 JSP 的一部分
  • Android蓝牙java.io.IOException:bt套接字已关闭,读取返回:-1

    我正在尝试编写一个代码 仅连接到运行 Android 5 0 KitKat 的设备上的 目前 唯一配对的设备 无论我尝试了多少方法 我仍然会收到此错误 这是我尝试过的最后一个代码 它似乎完成了我看到人们报告为成功的所有事情 有人能指出我做错
  • 在 MongoDB 和 Apache Solr 之间同步数据的简单方法

    我最近开始使用 MongoDB 和 Apache Solr 我使用 MongoDB 作为数据存储 并且希望 Apache Solr 为我的数据创建索引 以实现应用程序中的搜索功能 经过一些研究 我发现 基本上有两种方法可以在 MongoDB
  • 在 Java 中通过 XSLT 分解 XML

    我需要转换具有嵌套 分层 表单结构的大型 XML 文件
  • 如何删除日期对象的亚秒部分

    当 SQL 数据类型为时间戳时 java util Date 存储为 2010 09 03 15 33 22 246 如何在存储记录之前将亚秒设置为零 例如 在本例中为 246 最简单的方法是这样的 long time date getTi
  • 如何从日期中删除毫秒、秒、分钟和小时[重复]

    这个问题在这里已经有答案了 我遇到了一个问题 我想比较两个日期 然而 我只想比较年 月 日 这就是我能想到的 private Date trim Date date Calendar calendar Calendar getInstanc
  • 如何停止执行的 Jar 文件

    这感觉像是一个愚蠢的问题 但我似乎无法弄清楚 当我在 Windows 上运行 jar 文件时 它不会出现在任务管理器进程中 我怎样才能终止它 我已经尝试过 TASKKILL 但它对我也不起作用 On Linux ps ef grep jav
  • Play.application() 的替代方案是什么

    我是 Play 框架的新手 我想读取conf文件夹中的一个文件 所以我用了Play application classloader getResources Data json nextElement getFile 但我知道 play P
  • Java - 从 XML 文件读取注释

    我必须从 XML 文件中提取注释 我找不到使用 JDOM 或其他东西来让它们使用的方法 目前我使用 Regex 和 FileReader 但我不认为这是正确的方法 您可以使用 JDOM 之类的东西从 XML 文件中获取注释吗 或者它仅限于元
  • Karaf / Maven - 无法解决:缺少需求 osgi.wiring.package

    我无法在 Karaf 版本 3 0 1 中启动捆绑包 该包是使用 Maven 构建的并导入gson http mvnrepository com artifact com google code gson gson 2 3 1 我按照要求将
  • 我可以限制分布式应用程序发出的请求吗?

    我的应用程序发出 Web 服务请求 提供商处理的请求有最大速率 因此我需要限制它们 当应用程序在单个服务器上运行时 我曾经在应用程序级别执行此操作 一个对象跟踪到目前为止已发出的请求数量 并在当前请求超出允许的最大负载时等待 现在 我们正在
  • 禁用 Android 菜单组

    我尝试使用以下代码禁用菜单组 但它不起作用 菜单项仍然启用 你能告诉我出了什么问题吗 资源 菜单 menu xml menu menu
  • 记录类名、方法名和行号的性能影响

    我正在我的 java 应用程序中实现日志记录 以便我可以调试应用程序投入生产后可能出现的潜在问题 考虑到在这种情况下 人们不会奢侈地使用 IDE 开发工具 以调试模式运行事物或单步执行完整代码 因此在每条消息中记录类名 方法名和行号将非常有
  • 使用 JFreeChart 为两个系列设置不同的 y 轴

    我正在使用 JFreeChart 使用折线图绘制两个数据系列 XYSeries 复杂的因素是 其中一个数据系列的 y 值通常远高于第二个数据系列的 y 值 假设第一个系列的 y 值约为数百万数量级 而第二个数据系列的 y 值约为数百万数量级
  • 将对象从手机共享到 Android Wear

    我创建了一个应用程序 在此应用程序中 您拥有包含 2 个字符串 姓名和年龄 和一个位图 头像 的对象 所有内容都保存到 sqlite 数据库中 现在我希望可以在我的智能手表上访问这些对象 所以我想实现的是你可以去启动 启动应用程序并向左和向
  • 如何使用通配符模拟泛型方法的行为

    我正在使用 EasyMock 3 2 我想基于 Spring Security 为我的部分安全系统编写一个测试 我想嘲笑Authentication http docs spring io autorepo docs spring secu
  • 基于 Spring Boot 的测试中的上下文层次结构

    我的 Spring Boot 应用程序是这样启动的 new SpringApplicationBuilder sources ParentCtxConfig class child ChildFirstCtxConfig class sib

随机推荐

  • Field 'id' doesn't have a default value问题解决方法

    Field id doesn t have a default value问题解决方法 突然想温习温习对数据库的读写 于是就用mysql建了一张单独的表 见代码1 用Hibernate写了个应用 可以正常查询 修改数据了 开始时 数据是在m
  • IDEA——Java:程序包xxxx不存在终极方案总结

    最近在接手一个新的java项目 导入到IDEA后发现存在报错 程序包找不到 寻思应该是某些依赖没有加载进来 但几番尝试后发现问题依旧 于是决定调研下对应的解决方案 说实话类似这种问题的解决方案网上一搜一大堆 但试了很多根本不管用 其实大多数
  • Vue项目,通过数组下标更改数组的值不生效,页面没有重新渲染

    大家好 我是小梅 公众号 小梅的前端之路 原创作者 作为在前端领域不断探索的一员 在此记录开发中遇到的问题 如果你也遇到了相同的问题 希望本文对你有帮助 问题背景 今天在开发中遇到了一个需要 在列表里要通过按钮的点击控制手机号码列是显示正常
  • vue实现连接Mysql数据库和服务器通信

    在vue中 我们不仅是为了做前端页面展示和数据响应式 最重要的还是去访问后端请求 把数据给数据库存储 这样刷新页面数据才不会丢失 vue中对于数据的发送以及存储也很重要 稍不注意也会存在很大的问题 所以我们要学习一下存储数据以及对数据的请求
  • IDEA运行jar包不存在问题

    写在前面 本博客中解决方法适用情景为依赖包导入无错的情况下 即IDEA原因 解决方案 点击File gt Invaildate Cache Restart重启IDEA 2 若1无效 cmd路径切换到项目下执行mvn idea idea 再打
  • java 运行报错has been compiled by a more recent version of the Java Runtime (class file version 54.0)

    报错信息 Exception in thread main java lang UnsupportedClassVersionError pers cyz BookManage has been compiled by a more rec
  • p值小于0.05拒绝还是接受_25常见种误区:P值、置信区间和统计功效

    连享会主页 lianxh cn 连享会 小直播 Stata 数据清洗 第二季 连享会 计量专题 因果推断 内生性 专题 2020 11 12 15 主讲 王存同 中央财经大学 司继春 上海对外经贸大学 课程主页 https gitee co
  • Shell笔记--使用系统函数、自定义函数和Shell工具

    目录 1 basename和dirname系统函数 2 自定义函数 3 Shell常用工具 3 1 cut 3 2 sort 1 basename和dirname系统函数 basename 基本用法 basename string path
  • LiveData的使用及详解

    1 LiveData简单使用 本篇文章代码实现部分主要使用Java进行讲解 LiveData主要方便用于数据的观察 进行UI更新或者业务处理等操作 如下为LiveData的简单代码实现 创建一个MutableLiveData对象 这个使用L
  • ModuleNotFoundError: No module named ‘pygame’——Python3.6安装pip并下载pygame模块

    问题 今天学习python的时候 运行时报错 ModuleNotFoundError No module named pygame 意思就是没有 pygame 这个模块 解决办法 下载一下这个模块就行了 pip3 install pygam
  • 给你一个电商网站,你如何测试?

    当下软件测试主流方向是Web端和移动端应用 但无论是哪个端 多数都可以基于软件测试的六个方向来测试 即功能 性能 易用性 可靠性 兼容性 有效性这几个方面考虑 如果给你一个电商网站 你该如何测试 以下是测试重点 文末有福利 一 功能测试 链
  • 光学基础知识:焦点、弥散圆、景深、焦深

    1 焦点 focus 与光轴平行的光线射入凸透镜时 理想的镜头应该是所有的光线聚集在一点后 再以锥状的扩散开 来 这个聚集所有光线的一点 就叫做焦点 2 弥散圆 circle of confusion 在焦点前后 光线开始聚集和扩散 点的影
  • 原生js 传数组作为参数 之get /post

    1 get 方式 var getData questionIDs qustionArr var questionArr questionArr push 12345678 定义一个函数 关键就在这里 原生的请求传数组是行不通的 需要将数据做
  • Excalidraw本地化部署

    Excalidraw本地化部署 官方地址 https excalidraw com 为什么要本地话部署呢 因为官方不支持中文手写体 Excalidraw介绍 Excalidraw是一个开源 小巧易用的手写风格的在线绘图工具 本地部署 在te
  • mysql死锁分析工具show engine innodb status

    参考文章 记录一次MySQL死锁的分析与解决过程 mysql之show engine innodb status解读 把MySQL中的各种锁及其原理都画出来 写在开头 本文为学习后的总结 可能有不到位的地方 错误的地方 欢迎各位指正 前言
  • 单片机c语言如何表示二进制,单片机C语言中将二进制数转化为十进制的办法

    单片机C语言中将二进制数转化为十进制的办法 1 最简单最直观的方法 将2进制方式表示的数转化为10进制表示的数 要用除10取余法 步骤如下 被除数记为x 10进制表示的结果用数组a表示 1 i 0 2 a i x 10 x x 10 i 3
  • 图灵学院Java架构师课程,基于java

    主要功能模块 1 登录 输入账号密码和验证码登录 2 用户信息模块 3 菜单模块 4 角色模块 5 项目竞赛活动申请模块 6 项目竞赛经费申请模块 7 项目竞赛活动管理审批模块 8 项目个人赛报名模块 9 项目团队赛报名模块 10 项目结题
  • LINUX软中断-ksoftirqd

    前言 在上一篇 LINUX软中断 softirq的描述中 提到过ksoftirqd 这篇文章就介绍ksoftirqd ksoftirqd 是什么 ksoftirqd 是个内核线程 在创建的时候是绑定cpu的 每一个core对应生成一个kso
  • C++基础教程——递归

    介绍 程序调用自身的编程技巧称为递归 recursion 递归做为一种算法在程序设计语言中广泛应用 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法 它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解 递
  • java高并发系列之JMM相关的一些概念

    JMM java内存模型 由于并发程序要比串行程序复杂很多 其中一个重要原因是并发程序中数据访问一致性和安全性将会受到严重挑战 如何保证一个线程可以看到正确的数据呢 这个问题看起来很白痴 对于串行程序来说 根本就是小菜一碟 如果你读取一个变