Java 内存模型,或许应该这么理解

大家好,我是树哥。
在前面一段时间,我连续写了几篇关于并发编程的文章:
- 从 CPU 讲起,深入理解 Java 内存模型! - 陈树义的博客
- 深入理解 happens-before 原则 - 陈树义的博客
- 深入理解 synchronized 的锁优化 - 陈树义的博客
- 深入理解 Java 对象的内存布局 - 陈树义的博客
- 深入理解 volatile 关键字 - 陈树义的博客
这几篇文章分别讲了 Java 内存模型、happens-before 原则、volatile 关键字、synchronized 关键字、Java 对象的内存布局。这 5 篇文章看着好像是独立的,但实际上他们是互相关联的。
在好几年前我也看过 Java 内存模型这些内容,但网上的内容实在太多太杂,始终找不到合理的解释。刚好就在几周前,当我再次认真看这些内容的时候,突然发现能比较好地串起来了,所以就写了这几篇文章。今天,就树哥一起与你一起重温下这几个知识点的联系与理解吧。
Java 内存模型
网上关于 Java 内存模型的内容特别多,很多都讲到了多 CPU 与缓存的数据一致性问题,于是顺带牵出了 MESI 等缓存一致性协议。其实到这里都没问题,都挺有逻辑的。
但接下来为啥有 Java 内存模型?为啥又有 happens-before 原则?这些内容基本上没有一个说得清楚,这就让人很困惑了。此外,有些还扯出了内存屏障、执行时序的问题,但都没啥逻辑,听起来乱糟糟的。我就曾专门花了一个晚上认真看某篇很火的文章,但最终也没搞懂。
对于 Java 内存模型,我舍弃了一些不必要的细碎点,整理了我的一些理解,我感觉相对来说还是比较好理解的。
首先,由于多核 CPU 和高速缓存在存在,导致了缓存一致性问题。 这个问题属于硬件层面上的问题,而解决办法是各种缓存一致性协议。不同 CPU 采用的协议不同,MESI 是最经典的一个缓存一致性协议。
其次,操作系统作为对底层硬件的抽象,自然也需要解决 CPU 高速缓存与内存之间的缓存一致性问题。 各个操作系统都对 CPU 高速缓存与缓存的读写访问过程进行抽象,最终得到的一个东西就是「内存模型」。
从硬件到操作系统,这个是我自己的理解,我并没有找到一些资料提到这点。但我觉得这应该是没有错的。因为操作系统就是对底层硬件的抽象,而所有抽象的东西就需要定义一些概念。
对于操作系统来说,这些概念就是内存模型、CPU 时间片等。内存模型这个词,在操作系统的教科书上也是可以找到的,这也是一个佐证吧。
于是,我们从硬件层面理解到了操作系统层面,但这跟 Java 内存模型有啥关系呢?
最后,Java 语言作为运行在操作系统层面的高级语言,为了解决多平台运行的问题,在操作系统基础上进一步抽象,得到了 Java 语言层面上的内存模型,其也是为了解决多线程情况下的数据一致性问题。
我们是因为要实现 Java 语言的「Write Once, Run Anywhere」的理念,那么就必须解决多平台内存模型不一致的问题,这样才创造出了 Java 内存模型。
Java 内存模型规定了很多规则,如果 Java 程序能够遵守 Java 内存模型的规则,那么其写出的程序就是并发安全的,这就是 Java 内存模型最大的价值。
到这里,我们从硬件、操作系统再到语言层面,知道了 Java 内存模型诞生的原因,知道其诞生就是为了解决多平台的内存模型统一问题,进一步其实就是多线程的数据一致性问题。
happens-before 原则
前面说到,为了解决多平台的内存模型统一,以及多线程的数据一致性问题,所以有了 Java 内存模型。但是 Java 内存模型的内容太多了,基本就记不住,非常不利于编程人员理解,所以才有了 happens-before 原则。
所以说 happens-before 原则是对 Java 内存模型的简化,让我们更好地写出并发代码。
volatile 关键字
volatile 关键字,其实也与 Java 内存模型有关系,只是很多文章都没说清楚。
volatile 关键字有两个作用,就是可见性和禁止指令重排序。但为啥它有这两个作用呢?其实 volatile 这两个作用的来源,就来自于 Java 内存模型里对 volatile 变量定义的特殊规则。
这就是 volatile 关键字与 Java 内存模型的关系,比较简单。
至于内存屏障这个词,其实就是一个让我们方便理解的名词,诞生于 volatile 禁止指令重排序这个作用里,也没啥不好理解的。
synchronized 关键字
synchronized 关键字,也是并发编程常用到的内容,其实它和 Java 内存模型没关系,但和 Java 虚拟机规范有关系。
synchronized 关键字经过编译之后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令,这两个字节码的执行需要指明一个要锁定或解锁的对象。而 monitorenter 和 monitorexit 这两个字节码指令为啥能实现这样的功能,是因为 Java 虚拟机中做了强制定义,那么虚拟机就需要实现。
synchronized 关键字与 Java 对象的内存布局,也是有关系的。自旋锁、自适应锁、偏向锁,它们靠什么实现,就是 Java 对象中的对象头去判断,然后进行一系列的逻辑操作。
总结
至此,我们基本上可以把 Java 并发编程里常见的那些概念的关系搞清楚了。
Java内存模型 是对内存布局的抽象,解决多平台运行以及多线程一致性的问题。happens-before 原则 是 Java 内存模型定义的简化,方便我们学习。volatile 则是轻量级同步同步机制,其来源于 Java 内存模型赋予的权利。
synchronized 关键字的合法性,则来自于 Java 虚拟机规范。而 synchronized 中自旋锁、自适应锁、偏向锁等,都依靠 Java 对象的对象头 来判断。
以上就是我对 Java 并发编程里常见概念的理解,感觉还是比较清晰一些。如果有什么理解得不对的,欢迎一起探讨探讨~
Java 内存模型,或许应该这么理解的更多相关文章
- 对Java内存模型即JMM的理解
类似物理上的计算机系统,Java虚拟机规范中也定义了一种Java内存模型,即Java Memory Model(JMM),来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能 ...
- Java内存模型解惑--观深入理解Java内存模型系列文章有感(二)
1.volatile关键字修饰的域的特性 当我们声明共享变量为volatile后,对这个变量的读/写将会很特别.理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用 ...
- 全面理解Java内存模型(JMM)及volatile关键字(转载)
关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoad ...
- 全面理解Java内存模型(JMM)及volatile关键字(转)
原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型( ...
- Java内存模型原理,你真的理解吗?
[51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关实现规则,环环相扣,希望读者看完这篇文章后能对 Java 内存模型体系产生一个相对清晰的理解,知其然知其所以 ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- Java内存模型原理总结(转自51CTO)
转载地址:http://developer.51cto.com/art/201811/587220.htm [51CTO.com原创稿件]这篇文章主要介绍模型产生的问题背景,解决的问题,处理思路,相关 ...
- Java并发(1)- 聊聊Java内存模型
引言 在计算机系统的发展过程中,由于CPU的运算速度和计算机存储速度之间巨大的差距.为了解决CPU的运算速度和计算机存储速度之间巨大的差距,设计人员在CPU和计算机存储之间加入了高速缓存来做为他们之间 ...
- 13 Java内存模型
数据竞争 int a=0, b=0; public void method1() { int r2 = a; b = 1; } public void method2() { int r1 = b; ...
随机推荐
- python基础练习题(题目 打印出杨辉三角形前十行。)
day38 --------------------------------------------------------------- 实例061:杨辉三角 题目 打印出杨辉三角形前十行. 分析: ...
- Python标准库tempfile的使用总结
Python标准库tempfile的使用总结 临时文件是计算机程序存储临时数据的文件,它的扩展名通常是".temp".本文用于记录使用Python提供的临时文件API解决实际问题的 ...
- Mozi.HttpEmbedded嵌入式Web服务器
Mozi.HttpEmbedded 嵌入式Web服务器 项目介绍 Mozi.HttpEmbedded是一个基于.Net构建的嵌入式Web服务器,为.Net App提供web服务功能. 嵌入式的目标不是 ...
- come on! 基于LinkedHashMap实现LRU缓存
/** * @Description 基于LinkedHashMap实现一个基于'LRU最近最少使用'算法的缓存,并且最多存MAX个值 * @Author afei * @date:2021/4/25 ...
- 浅尝Spring注解开发_自定义注册组件、属性赋值、自动装配
Spring注解开发 浅尝Spring注解开发,基于Spring 4.3.12 包含自定义扫描组件.自定义导入组件.手动注册组件.自动注入方法和参数.使用Spring容器底层组件等 配置 @Confi ...
- IDEA新建项目时的默认配置与模版配置
今天一大早,群里(点击加群)有小伙伴问了这样的一个问题: 在我们使用IDEA开发项目的时候,通常都会有很多配置项需要去设置,比如对于Java项目来说,一般就包含:JDK配置.Maven配置等.那么如果 ...
- 浅谈 Linux IO
公众号关注 「开源Linux」 回复「学习」,有我为您特别筛选的学习资料~ 来源于:360云计算 1 前言 Linux IO是文件存储的基础.本文参考了网上博主的一些文章,主要总结了LinuxIO的基 ...
- p2p-tunnel 打洞内网穿透系列(三)TCP转发访问内网web服务
系列文章 p2p-tunnel 打洞内网穿透系列(一)客户端配置及打洞 p2p-tunnel 打洞内网穿透系列(二)TCP转发访问远程共享文件夹 p2p-tunnel 打洞内网穿透系列(三)TCP转发 ...
- Flask01 第一个flask项目
参考地址:https://github.com/miguelgrinberg/microblog/tree/v0.1 flask环境[苹果M1] 添加虚拟环境 python3 -m venv venv ...
- 用Docker打包Python运行环境
虽然Docker作为部署环境打包镜像的工具,和我的科研并没有直接的关系.但我觉得在项目中运用Docker来打包环境依赖也可以大大提高工作效率,于是准备专门学习一下Docker. 1. Docker基础 ...