热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java内存模型之可见性分析

Jav

1. JMM模型描述
  • 给定程序以及一个检测程序是否合法的执行跟踪,JMM工作原理是检查执行跟踪中的每个读,并根据某些规则检查读观察到的写是否有效

  • JMM中可能产生的行为表现为不论代码是如何实现程序行为,只要保证程序的所有结果执行和JMM预期的结果一致即可

  • 基于上述的第二点,对实现者执行的代码进行转换的实现就比较自由,可以实现操作的重排序甚至删除不必要的同步操作代码

2. JMM之数据共享与竞争

线程共享与独占区域

  • 线程共享区域: JVM运行数据区中的方法区,堆内存存储的数据变量,存在数据竞争,即数据读写的安全问题

  • 线程独占区域: JVM为每个线程单独创建的私有区域,用于存储当前线程私有的数据变量,不存在数据竞争,比如线程局部变量,ThreadLocal/ThreadLocalRandom等

线程通信产生数据竞争

  • 简要的源代码

// constant.java
final int P = 10;
final int C = 20;
// shared.java
int pwrite = 0;
int cwrite = 0;
// producer.java
int pread = 0;
public void run(){
pread = cwrite; // 生产者线程需要消费者线程cwrite的数据
pwrite = P;
}
// consumer.java
int cread = 0;
public void run(){
cread = pwrite;// 消费者线程需要生产者线程的pwrite数据
cwrite = C;
}
//按正常结果输出的预期值推断,不会产生同时pread == C(20)cread == P(10)

  • 结果反推分析(基于我们看到的代码顺序)

    • 如果上述的执行结果成立,那么cwrite = C
      一定是在pread = cwrite
      之前执行的;

    • 由于cwrite = C
      是在cread = pwrite
      之后执行,所以cread = pwrite
      一定是在pread = cwrite
      之前执行的;

    • 也就是cread = pwrite
      一定是在pwrite = P
      之前执行的,所以结果是不成立

  • 产生问题

    • 线程既然存在写操作,那么写操作的数据变量一定会让另一个线程读取到对应写后的数据么?

    • 由于线程本身也有自己的工作内存,因此读取数据变量不一定就是另一个线程写操作之后的数据,此时可能读取到工作内存上的缓存数据(脏数据)

  • 同时基于JMM规范,产生优化后可能执行的代码

public void run(){
pread = cwrite; // 生产者线程需要消费者线程cwrite的数据 --1
pwrite = P; // --2
}
// consumer.java
int cread = 0;
public void run(){
cwrite = C; // --3
cread = pwrite;// 消费者线程需要生产者线程的pwrite数据 -4
}

  • 在上述两个线程中分析

    • 线程在并发下,可能产生执行的顺序为3-1-2-4,也就是同时产生pread == C(20)和cread == P(10)

  • 数据竞争

    • 当前线程对一个变量执行写操作

    • 同时另一个线程对相同的变量执行读操作

    • 读写操作没有通过同步实现排序

  • 产生问题

    • 不同线程之间通信会对共享变量的数据产生竞争,在这种情况下,JMM作出重排序的优化会导致输出结果与预期的结果不一致,如果放在实际的业务场景中,将会导致很多无法控制的业务逻辑错误,后果不可想象.

JMM下的并发问题

  • 其一,读取到的共享数据不一定是写操作之后的数据,也就是写操作对读操作不可见(缓存导致)

  • 其二,JMM为了提升性能对代码进行重排序,那么就会导致数据产生的结果和预期的不一致(重排序导致)

3. JMM可见性解决方案

线程之工作内存

  • JMM抽象之工作内存(线程本地内存)

    • 线程栈中的存储的变量,如局部变量,方法参数,异常处理参数等

    • CPU高速缓存

  • 线程,工作内存,JMM与主内存

从上述可知,在JVM运行数据区中,工作内存与主内存是通过JMM模型规范来完成彼此之间的数据交互,因此可以通过JMM定义的内存语义规范来提供数据变量的可见性

  • 基于缓存问题解决方案

    • JMM规范规定使用针对的技术手段时,将强制线程直接绕过工作内存读取主内存的共享数据

    • 常用技术手段:volatile/synchronized/final/具有内存同步的操作指令

重排序

  • 遵循规则

    • as-if-serial: 即不管怎么进行重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变.编译器/runtime/处理器都必须遵循as-if-serial语义,也就是说编译器和处理器不会对存在数据依赖关系的操作做重排序

  • 重排序的分类

    • 编译器重排序: 基于单个线程程序的语义前提下,Java开启server模式(clinet不支持)可以对程序代码进行编译优化

    • 处理器重排序:在没有存在数据依赖的前提下,处理器可以改变机器指令的执行顺序

  • 重排序解决方案

    • 编译器会根据JMM特定类型(同步代码标志等)禁止进行重排序

    • 在Java编译器生成指令之前插入特定的内存屏障来禁止处理器重排序

  • 内存屏障类型: 参见CPU高速缓存与内存屏障

感谢花时间阅读,如果有用欢迎转发或者点个好看,谢谢!!!



推荐阅读
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • Java自带的观察者模式及实现方法详解
    本文介绍了Java自带的观察者模式,包括Observer和Observable对象的定义和使用方法。通过添加观察者和设置内部标志位,当被观察者中的事件发生变化时,通知观察者对象并执行相应的操作。实现观察者模式非常简单,只需继承Observable类和实现Observer接口即可。详情请参考Java官方api文档。 ... [详细]
  • This article discusses the efficiency of using char str[] and char *str and whether there is any reason to prefer one over the other. It explains the difference between the two and provides an example to illustrate their usage. ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • OpenMap教程4 – 图层概述
    本文介绍了OpenMap教程4中关于地图图层的内容,包括将ShapeLayer添加到MapBean中的方法,OpenMap支持的图层类型以及使用BufferedLayer创建图像的MapBean。此外,还介绍了Layer背景标志的作用和OMGraphicHandlerLayer的基础层类。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • 本文介绍了一个在线急等问题解决方法,即如何统计数据库中某个字段下的所有数据,并将结果显示在文本框里。作者提到了自己是一个菜鸟,希望能够得到帮助。作者使用的是ACCESS数据库,并且给出了一个例子,希望得到的结果是560。作者还提到自己已经尝试了使用"select sum(字段2) from 表名"的语句,得到的结果是650,但不知道如何得到560。希望能够得到解决方案。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了安全性要求高的真正密码随机数生成器的概念和原理。首先解释了统计学意义上的伪随机数和真随机数的区别,以及伪随机数在密码学安全中的应用。然后讨论了真随机数的定义和产生方法,并指出了实际情况下真随机数的不可预测性和复杂性。最后介绍了随机数生成器的概念和方法。 ... [详细]
author-avatar
徐英涛19-85
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有