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基础练习题(题目 画圈,学用circle画圆形。)
day37 --------------------------------------------------------------- 实例056:画圈 题目 画图,学用circle画圆形. 分析 ...
- python基础练习题(题目 计算两个矩阵相加)
day30 --------------------------------------------------------------- 实例044:矩阵相加 题目 计算两个矩阵相加. 分析:矩阵可 ...
- nginx从入门到入坟
目录 1.nginx下载安装 2.启动命令 3.服务脚本 4.nginx目录说明 5.nginx配置文件 6.虚拟主机与域名配置和站点配置 6.1server_name配置规则 7.反向代理负载均衡配 ...
- Linux Centos7 根分区磁盘扩容[详解]
CentOS7 根分区扩容 [详细过程] 前提 1.如果原来的系统根分区为逻辑卷分区 则可以使用如下的方法 如果不是则不可以 2.如果原来的系统根分区不是逻辑卷分区 则不可以扩展只能再添加挂在磁盘进行 ...
- 【深度学习 论文篇 03-2】Pytorch搭建SSD模型踩坑集锦
论文地址:https://arxiv.org/abs/1512.02325 源码地址:http://github.com/amdegroot/ssd.pytorch 环境1:torch1.9.0+CP ...
- 干货|带你体验一次原生OpenStack云平台发放云主机的过程
一个执着于技术的公众号 1 前言 上一章节我们完成了OpenStack云平台的搭建工作,今天就带大家一起学习下如何发放一台云主机 点击查看:如何搭建一套OpenStack云平台 2 发放OpenSta ...
- linux fedora35 配置jdk,安装mysql,安装tomcat
配置jdk18很简单,下载jdk,只需要.tar.gz结尾的文件就行,https://download.oracle.com/java/18/latest/jdk-18_linux-x64_bin.t ...
- Nginx的常用配置
Nginx配置文件结构 设置worker进程的用户,指的linux中的用户,会涉及到nginx操作目录或文件的一些权限,默认为 nobodyuser root; worker进程工作数设置,一般来说C ...
- ajax 请求登录超时跳转登录页解决方法
在Filter里判断是否登录,如果未登录返回401状态 public class SelfOnlyAttribute : ActionFilterAttribute { public override ...
- netty系列之:netty中常用的xml编码解码器
目录 简介 XmlFrameDecoder XmlDecoder 总结 简介 在json之前,xml是最常用的数据传输格式,虽然xml的冗余数据有点多,但是xml的结构简单清晰,至今仍然运用在程序中的 ...