摘要:本文的目的来理解 J V M 与我们的内存两者之间是如何协调工作的。

本文分享自华为云社区《一文带你图解Java内存模型》,作者: 龙哥手记 。

我们今天要特别重点讲的,也就是我们本文的目的来理解 J V M 与我们的内存两者之间是如何协调工作的,它的名字就是Java内存模型(JMM)。

一 打牢基础

原子性是一种按原子方式的操作,那你有可能问了“原子方式”是啥?就是不可中断的意思。你也可以理解不能再分。要么不执行,要么用原子的方式来执行,在这个过程中是不会被其他线程中断。

有什么栗子吗?

眼见为实​

 class Data{
AtomicInteger atomicInteger = new AtomicInteger();
volatile int number=0;
public void numberIncrement(){
this.number++;
} public void atomicIntegerIncrement(){
this.atomicInteger.incrementAndGet();
}
}

public class Main {

public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
data.numberIncrement();
data.atomicIntegerIncrement();
}
},"t"+i).start();
}

while (Thread.activeCount() > 2){
Thread.yield();
} System.out.println("volatile修饰的int type:"+data.number);
System.out.println("原子类:"+data.atomicInteger);
}
}​

再看下不是原子性的案例​

 class Data{
volatile int number=0;
public void numberIncrement(){
this.number++;
}
}
public class Main {

public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 10; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
data.numberIncrement();
}
},"t"+i).start();
}

while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(data.number);
}
}​

这个程序目的是 10 个线程把 number 变为 10000,因为 volatile 不保证原子性,所以是达不到效果的.输出结果如下:

这两操作是原子性的,也就是顺序执行且不能被打断的,要么都执行成功,要么都失败

可见性是线程对共享变量修改的可见状态。假如一个线程修改了一个共享变量的值,其他线程立马知道共享变量改了。比较好的例子就是 volatile 变量了。这里叙述下大致的原理:

首先你的 volatile 变量对所有的线程都是可见的,指的是你执行完 assign 之后立即就会把共享变量复制到主内存上去;在其他任意一个线程读取主内存对象时候,读取都是存到自己的线程私有内存里面,它是都会刷新主内存。这仅仅是针对同一个线程,在主内存上是表现数据一致性的。但是那如果是其他线程的私有内存它们一起来存取到各其他线程的私有内存,那你私有内存和你的主内存的数据那可就不一定相同啊。这就是 volatile 它是不能保证啥?不能保证线程安全的。

怎么样让它线程安全呢?

  • 第一个条件:运算结果并不依赖变量的当前值,或者你能保证只有一个线程修改变量的值,就是上面我说的第一种情况。

  • 第二个条件:变量不需要和其它的状态变量共同参与不变约束。

最后一个有序性意思说如果在本线程内观察,所有的操作都是有序的,说明线程间的操作具有有序性。那肯定有无序的,我们可以用java为我们提供好的 volatile 和 synchronized 两个关键字来保证线程之间操作有序就完成。

先来回顾下指令重排序

因为在JVM内部,我们为了提高性能,编译器和处理器会对指令做重排序,但是JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的 Memory Barrier,

有序性是指:按照代码的既定顺序执行。

说的通俗一点,就是代码会按照指定的顺序执行,例如,按照程序编写的顺序执行,先执行第一行代码,再执行第二行代码,然后是第三行代码,以此类推。如下图所示。

指令重排序 编译器或者解释器为了优化程序的执行性能,有时会改变程序的执行顺序。但是,编译器或者解释器对程序的执行顺序进行修改,可能会导致意想不到的问题!

在单线程下,指令重排序可以保证最终执行的结果与程序顺序执行的结果一致,但是在多线程下就会存在问题。

如果发生了指令重排序,则程序可能先执行第一行代码,再执行第三行代码,然后执行第二行代码,如下所示。

数据依赖性

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,

好了我们要先整明白它有啥用?

它规定了一个线程如何并且能够及时看到其他线程修改过后的变量的值,及如何到内存去同步咱们的共享变量。

happens-before先行发生原则

它用于描述两个操作在内存中的可见性,这样可以判断数据是否存在竞争,线程是否安全的主要根据。​

 int a = 10;
b = b + 1;​

CPU有时候会为了计算单元的利用率将其进行指令重排,如果b = b + a 就不会进行指令重排,因为b的结果依赖于 a 的值。

二 JVM对内存模型的实现

在JVM内部,内存模型大致分为两大块:线程栈区和堆。如图:

JVM中运行的每个线程都有自己的线程栈,线程栈包含了当前线程执行的方法调用相关信息,我们也可以叫它调用栈。

从上图得出,线程A和线程B之间如果要通信的话,必须要经历下面2个步骤:

首先,线程A里面已更新的共享变量刷新到主内存里面去。 然后,线程B到主内存去读取线程A之前已更新过的共享变量。

画图说明这两个步骤:

本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(我们先假设值为1)临时存放在自己的本地内存A中。假如它们两个需要通信了,线程A首先把自己本地内存的x值变成了1。随后,线程B到主内存中读取线程A更新后的x值,此时线程B的本地内存的x值也变成了1。

它是咋来的呢?

JVM规范由它来定义这玩意,你想吗,内存模型,内存模型,就是告诉你在JVM中你的内存是如何分布的。根据它特有的结构,就它的结构自然而然的表示出来它的功能。它的结构,我们先瞄一眼

看到上面图没有,小伙伴们先回忆概念:

Heap

优点:运行时数据区,动态分配内存大小,有 gc; 缺点:因为要在运行时动态分配内存,所以它的存取速度比栈要慢一些,对象是放在堆上,静态类型和那个类的定义也是一起存储在堆上的。

stack

优点:存取速度比 Heap 快,但是肯定比寄存器要慢一丢丢。 缺点:由于是JVM提前划分好的,那它的数据大小和生命周期那就是确定的了,说明缺乏灵活性,你想你下有哪些用到的类型它的大小是固定的呢!莫错,基本数据类型,那就多得很。(譬如char, boolean, double, int等,提示一下对象句柄也属于基本类型变量的哦)。

当一个线程去访问一个对象时, 可以去访问对象的成员变量, 如果有两个线程访问对象的成员变量,则每个线程都有对象的成员变量的私有拷贝。

读完你也许一脸懵逼,这是啥?

正如上面讲到的,Java内存模型和硬件内存结构并不一致。硬件内存里面没有区分堆和栈,

点击关注,第一时间了解华为云新鲜技术~

用实例带你深入理解Java内存模型的更多相关文章

  1. 【并发编程】一文带你读懂深入理解Java内存模型(面试必备)

    并发编程这一块内容,是高级资深工程师必备知识点,25K起如果不懂并发编程,那基本到顶.但是并发编程内容庞杂,如何系统学习?本专题将会系统讲解并发编程的所有知识点,包括但不限于: 线程通信机制,深入JM ...

  2. 深入理解 Java 内存模型(一)- 内存模型介绍

    深入理解 Java 内存模型(一)- 内存模型介绍 深入理解 Java 内存模型(二)- happens-before 规则 深入理解 Java 内存模型(三)- volatile 语义 深入理解 J ...

  3. 深入理解Java内存模型(摘)

    --摘自 周志明<深入理解Java虚拟机> 转自 https://www.jianshu.com/p/15106e9c4bf3 深入理解Java内存模型(摘) java内存模型(Java ...

  4. 全面理解Java内存模型(JMM)及volatile关键字(转载)

    关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...

  5. 深入理解java内存模型

    深入理解Java内存模型(一)——基础 深入理解Java内存模型(二)——重排序 深入理解Java内存模型(三)——顺序一致性 深入理解Java内存模型(四)——volatile 深入理解Java内存 ...

  6. 全面理解Java内存模型(JMM)及volatile关键字(转)

    原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...

  7. 十二、深入理解Java内存模型

    深入理解Java内存模型 [1]CPU和缓存的一致性 ​ 我们应该都知道,计算机在执行程序的时候,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道.而计算机上面的数据,是存放在主存当 ...

  8. 深入理解java内存模型系列文章

    转载关于java内存模型的系列文章,写的非常好. 深入理解java内存模型(一)--基础 深入理解java内存模型(二)--重排序 深入理解java内存模型(三)--顺序一致性 深入理解java内存模 ...

  9. 【Todo】【转载】深入理解Java内存模型

    提纲挈领地说一下Java内存模型: 什么是Java内存模型 Java内存模型定义了一种多线程访问Java内存的规范.Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几 ...

随机推荐

  1. 在pyqt5中展示pyecharts生成的图像

    技术背景 虽然现在很少有人用python去做一些图形化的界面,但是不得不说我们在日常大部分的软件使用中都还是有可视化与交互这样的需求的.因此pyqt5作为一个主流的python的GUI框架地位是非常重 ...

  2. 学习javaScript必知必会(6)~类、类的定义、prototype 原型、json对象

    一.定义类:使用的是funciton,因为在js中没有定义类的class语句,只有function. ■ 举例: //定义一个Person类(通过类的无参构造函数定义类) function Perso ...

  3. Teamcenter无法创建多余账号怎么办?

    西门子的产品Teamcenter,用户账号的许可是命名的许可类型,数量是限定的:例如,账号许可购买了25个,那么活动账号已经达到25了,再创建第26个账号将无法创建.没办法创建多余的账号,怎么办? 当 ...

  4. Git命令中波浪号~与脱字符^的区别

    0.前言 波浪号~,英文名叫 tilde.脱字符^,英文名叫caret. 这两种符号常见于git reset的情景,简单的项目结构和操作一般不会涉及到两者之间的区别,似乎用哪个都可以.如果遇到比较繁杂 ...

  5. 今天太开心了,因为我知道了seastar框架

    今天听说了一个新的C++语言开发的网络框架,叫做seastar. seastar有何特别之处呢?先看看官网提供的性能数据: 性能 HTTPD benchmark: cpu # request/sec ...

  6. python第三方模块详细教程(紧急情况宝典)

    目录 一:第三方模块 第三方模块:并不是python自带的 需要基于网络下载! 使用下载第三方模块 需要将python pip添加到环境变量 1.验证pip路径添加成功 2.pycharm添加第三方模 ...

  7. kubernetes之kubeadm 安装kubernetes 高可用集群

    1. 架构信息 系统版本:CentOS 7.6 内核:3.10.0-957.el7.x86_64 Kubernetes: v1.14.1 Docker-ce: 18.09.5 推荐硬件配置:4核8G ...

  8. 学Go语言能找到实习吗,年前聊聊Go和Java

    前言 快过年了,来公司的人越来越少,估计明天都没什么人了,白泽也要收拾收拾回老家过年了.今天就随便写写零碎的事,所以行文当中难免思路跳跃,请大家一笑了之. 每次冷不丁收到公司给发的礼品袋,心头总是莫名 ...

  9. Android系统编程入门系列之硬件交互——通信硬件Bluetooth

    通信硬件NFC的文章,虽然可以在Android系统中通过非直接接触的形式与支持NFC硬件的设备通信,但是也只能交互一些简短的标签内容,对大量的持续性数据,却并不能很好的支持.因此针对这个弊端,可以考虑 ...

  10. Atcoder ARC-065

    ARC065(2020.8.3) A 尛你 \(\%\%\%\) B 翻译是错的,自闭了好久.先并查集将地铁连接的连通块联通,再枚举每条道路边判断是否在同一连通块即可. C 这翻译不知道在讲什么,建议 ...