再有人问你Java内存模型是什么,就把这篇文章发给他

转载自  再有人问你Java内存模型是什么,就把这篇文章发给他

前几天,发了一篇文章,介绍了一下JVM内存结构、Java内存模型以及Java对象模型之间的区别。有很多小伙伴反馈希望可以深入的讲解下每个知识点。Java内存模型,是这三个知识点当中最晦涩难懂的一个,而且涉及到很多背景知识和相关知识。

网上有很多关于Java内存模型的文章,在《深入理解Java虚拟机》和《Java并发编程的艺术》等书中也都有关于这个知识点的介绍。但是,很多人读完之后还是搞不清楚,甚至有的人说自己更懵了。本文,就来整体的介绍一下Java内存模型,目的很简单,让你读完本文以后,就知道到底Java内存模型是什么,为什么要有Java内存模型,Java内存模型解决了什么问题等。

本文中,有很多定义和说法,都是笔者自己理解后定义出来的。希望能够让读者可以对Java内存模型有更加清晰的认识。当然,如有偏颇,欢迎指正。

 

为什么要有内存模型

在介绍Java内存模型之前,先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情。要说计算机的内存模型,就要说一下一段古老的历史,看一下为什么要有内存模型。

内存模型,英文名Memory Model,他是一个很老的老古董了。他是与计算机硬件有关的一个概念。那么我先给你介绍下他和硬件到底有啥关系。

CPU和缓存一致性

我们应该都知道,计算机在执行程序的时候,每条指令都是在CPU中执行的,而执行的时候,又免不了要和数据打交道。而计算机上面的数据,是存放在主存当中的,也就是计算机的物理内存啦。

刚开始,还相安无事的,但是随着CPU技术的发展,CPU的执行速度越来越快。而由于内存的技术并没有太大的变化,所以从内存中读取和写入数据的过程和CPU的执行速度比起来差距就会越来越大,这就导致CPU每次操作内存都要耗费很多等待时间。

这就像一家创业公司,刚开始,创始人和员工之间工作关系其乐融融,但是随着创始人的能力和野心越来越大,逐渐和员工之间出现了差距,普通员工原来越跟不上CEO的脚步。老板的每一个命令,传到到基层员工之后,由于基层员工的理解能力、执行能力的欠缺,就会耗费很多时间。这也就无形中拖慢了整家公司的工作效率。

可是,不能因为内存的读写速度慢,就不发展CPU技术了吧,总不能让内存成为计算机处理的瓶颈吧。

所以,人们想出来了一个好的办法,就是在CPU和内存之间增加高速缓存。缓存的概念大家都知道,就是保存一份数据拷贝。他的特点是速度快,内存小,并且昂贵。

那么,程序的执行过程就变成了:

当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中。

之后,这家公司开始设立中层管理人员,管理人员直接归CEO领导,领导有什么指示,直接告诉管理人员,然后就可以去做自己的事情了。管理人员负责去协调底层员工的工作。因为管理人员是了解手下的人员以及自己负责的事情的。所以,大多数时候,公司的各种决策,通知等,CEO只要和管理人员之间沟通就够了。

而随着CPU能力的不断提升,一层缓存就慢慢的无法满足要求了,就逐渐的衍生出多级缓存。

按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存(L1),二级缓存(L3),部分高端CPU还具有三级缓存(L3),每一级缓存中所储存的全部数据都是下一级缓存的一部分。

这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。

那么,在有了多级缓存之后,程序的执行就变成了:

当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。

随着公司越来越大,老板要管的事情越来越多,公司的管理部门开始改革,开始出现高层,中层,底层等管理者。一级一级之间逐层管理。

单核CPU只含有一套L1,L2,L3缓存;

如果CPU含有多个核心,即多核CPU,则每个核心都含有一套L1(甚至和L2)缓存,而共享L3(或者和L2)缓存。

公司也分很多种,有些公司只有一个大Boss,他一个人说了算。但是有些公司有比如联席总经理、合伙人等机制。

单核CPU就像一家公司只有一个老板,所有命令都来自于他,那么就只需要一套管理班底就够了。

多核CPU就像一家公司是由多个合伙人共同创办的,那么,就需要给每个合伙人都设立一套供自己直接领导的高层管理人员,多个合伙人共享使用的是公司的底层员工。

还有的公司,不断壮大,开始差分出各个子公司。各个子公司就是多个CPU了,互相之前没有共用的资源。互不影响。

下图为一个单CPU双核的缓存结构。

随着计算机能力不断提升,开始支持多线程。那么问题就来了。我们分别来分析下单线程、多线程在单核CPU、多核CPU中的影响。

单线程。cpu核心的缓存只被一个线程访问。缓存独占,不会出现访问冲突等问题。

单核CPU,多线程。进程中的多个线程会同时访问进程中的共享数据,CPU将某块内存加载到缓存后,不同线程在访问相同的物理地址的时候,都会映射到相同的缓存位置,这样即使发生线程的切换,缓存仍然不会失效。但由于任何时刻只能有一个线程在执行,因此不会出现缓存访问冲突。

多核CPU,多线程。每个核都至少有一个L1 缓存。多个线程访问进程中的某个共享内存,且这多个线程分别在不同的核心上执行,则每个核心都会在各自的caehe中保留一份共享内存的缓冲。由于多核是可以并行的,可能会出现多个线程同时写各自的缓存的情况,而各自的cache之间的数据就有可能不同。

在CPU和主存之间增加缓存,在多线程场景下就可能存在缓存一致性问题,也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。

如果这家公司的命令都是串行下发的话,那么就没有任何问题。

如果这家公司的命令都是并行下发的话,并且这些命令都是由同一个CEO下发的,这种机制是也没有什么问题。因为他的命令执行者只有一套管理体系。

如果这家公司的命令都是并行下发的话,并且这些命令是由多个合伙人下发的,这就有问题了。因为每个合伙人只会把命令下达给自己直属的管理人员,而多个管理人员管理的底层员工可能是公用的。

比如,合伙人1要辞退员工a,合伙人2要给员工a升职,升职后的话他再被辞退需要多个合伙人开会决议。两个合伙人分别把命令下发给了自己的管理人员。合伙人1命令下达后,管理人员a在辞退了员工后,他就知道这个员工被开除了。而合伙人2的管理人员2这时候在没得到消息之前,还认为员工a是在职的,他就欣然的接收了合伙人给他的升职a的命令。

处理器优化和指令重排

上面提到在在CPU和主存之间增加缓存,在多线程场景下会存在缓存一致性问题。除了这种情况,还有一种硬件问题也比较重要。那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行乱序执行处理。这就是处理器优化

除了现在很多流行的处理器会对代码进行优化乱序处理,很多编程语言的编译器也会有类似的优化,比如Java虚拟机的即时编译器(JIT)也会做指令重排

可想而知,如果任由处理器优化和编译器对指令重排的话,就可能导致各种各样的问题。

关于员工组织调整的情况,如果允许人事部在接到多个命令后进行随意拆分乱序执行或者重排的话,那么对于这个员工以及这家公司的影响是非常大的。

 

并发编程的问题

前面说的和硬件有关的概念你可能听得有点蒙,还不知道他到底和软件有啥关系。但是关于并发编程的问题你应该有所了解,比如原子性问题,可见性问题和有序性问题。

其实,原子性问题,可见性问题和有序性问题。是人们抽象定义出来的。而这个抽象的底层问题就是前面提到的缓存一致性问题、处理器优化问题和指令重排问题等。

这里简单回顾下这三个问题,并不准备深入展开,感兴趣的读者可以自行学习。我们说,并发编程,为了保证数据的安全,需要满足以下三个特性:

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

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

有没有发现,缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题。所以,后文将不再提起硬件层面的那些概念,而是直接使用大家熟悉的原子性、可见性和有序性。

 

什么是内存模型

前面提到的,缓存一致性问题、处理器器优化的指令重排问题是硬件的不断升级导致的。那么,有没有什么机制可以很好的解决上面的这些问题呢?

最简单直接的做法就是废除处理器和处理器的优化技术、废除CPU缓存,让CPU直接和主存交互。但是,这么做虽然可以保证多线程下的并发问题。但是,这就有点因噎废食了。

所以,为了保证并发编程中可以满足原子性、可见性及有序性。有一个重要的概念,那就是——内存模型。

为了保证共享内存的正确性(可见性、有序性、原子性),内存模型定义了共享内存系统中多线程程序读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的一致性、原子性和有序性。

内存模型解决并发问题主要采用两种方式:限制处理器优化使用内存屏障。本文就不深入底层原理来展开介绍了,感兴趣的朋友可以自行学习。

 

什么是Java内存模型

前面介绍过了计算机内存模型,这是解决多线程场景下并发问题的一个重要规范。那么具体的实现是如何的呢,不同的编程语言,在实现上可能有所不同。

我们知道,Java程序是需要运行在Java虚拟机上面的,Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范。

提到Java内存模型,一般指的是JDK 5 开始使用的新的内存模型,主要由JSR-133: JavaTM Memory Model and Thread Specification 描述。感兴趣的可以参看下这份PDF文档(http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf)

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。

而JMM就作用于工作内存和主存之间数据同步过程。他规定了如何做数据同步以及什么时候做数据同步。

这里面提到的主内存和工作内存,读者可以简单的类比成计算机内存模型中的主存和缓存的概念。特别需要注意的是,主内存和工作内存与JVM内存结构中的Java堆、栈、方法区等并不是同一个层次的内存划分,无法直接类比。《深入理解Java虚拟机》中认为,如果一定要勉强对应起来的话,从变量、主内存、工作内存的定义来看,主内存主要对应于Java堆中的对象实例数据部分。工作内存则对应于虚拟机栈中的部分区域。

所以,再来总结下,JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。目的是保证并发编程场景中的原子性、可见性和有序性。

 

Java内存模型的实现

了解Java多线程的朋友都知道,在Java中提供了一系列和并发处理相关的关键字,比如volatilesynchronizedfinalconcurren包等。其实这些就是Java内存模型封装了底层的实现后提供给程序员使用的一些关键字。

在开发多线程的代码的时候,我们可以直接使用synchronized等关键字来控制并发,从来就不需要关心底层的编译器优化、缓存一致性等问题。所以,Java内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。

本文并不准备把所有的关键字逐一介绍其用法,因为关于各个关键字的用法,网上有很多资料。读者可以自行学习。本文还有一个重点要介绍的就是,我们前面提到,并发编程要解决原子性、有序性和一致性的问题,我们就再来看下,在Java中,分别使用什么方式来保证。

原子性

在Java中,为了保证原子性,提供了两个高级的字节码指令monitorentermonitorexit。在synchronized的实现原理文章中,介绍过,这两个字节码,在Java中对应的关键字就是synchronized

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

可见性

Java内存模型是通过在变量修改后将新值同步回主内存,在变量读取前从主内存刷新变量值的这种依赖主内存作为传递媒介的方式来实现的。

Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。

除了volatile,Java中的synchronizedfinal两个关键字也可以实现可见性。只不过实现方式不同,这里不再展开了。

有序性

在Java中,可以使用synchronizedvolatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作。

好了,这里简单的介绍完了Java并发编程中解决原子性、可见性以及有序性可以使用的关键字。读者可能发现了,好像synchronized关键字是万能的,他可以同时满足以上三种特性,这其实也是很多人滥用synchronized的原因。

但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。

 

总结

在读完本文之后,相信你应该了解了什么是Java内存模型、Java内存模型的作用以及Java中内存模型做了什么事情等。

关于Java中这些和内存模型有关的关键字,希望读者还可以继续深入学习,并且自己写几个例子亲自体会一下。可以参考《深入理解Java虚拟机》和《Java并发编程的艺术》两本书。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324665.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC对Ajax请求的处理

SpringMVC对Ajax请求的处理 [1] 问题: 当浏览器发起一个ajax请求给服务器,服务器调用对应的单元方法处理ajax请求。 而ajax的请求在被处理完成后,其处理结果需要直接响应。而目前我们在单元方 法中响应ajax请求,使用的是response对象&#x…

ssl2344P2835-刻录光盘【Floyd,联通块数,图论】

正题 洛谷题目 就是给出一个图,求最小联通块数。 输入输出(需要自取) Input 先是一个数N,接下来N行,分别表示各个营员愿意把自己获得的资料拷贝给其他哪些营员。即输入数据的第N1行表示第i个营员愿意把资料拷贝给那…

为了金秋那沉甸甸的麦穗,我绝不辜负春天

本文原创:焦文宇我可以一落千丈,但我就是要一鸣惊人。——题记01 没有谁的一生是一帆风顺的,也没有谁的一生是充满坎坷的。人生就像是一场游戏“玩”的好坏全都在于自己,我们没有任何理由去抱怨生活中的点滴,我们应当对…

BigDecimal丢失精度的坑

问题:new BigDecimal(double d)的数值居然还是不精确的 double d 0.09; BigDecimal bigDecimalnew BigDecimal(d); System.out.println(bigDecimal); System.out.println(d); 输出结果: 0.0899999999999999966693309261245303787291049957275390625 …

编译型语言VS解释型语言

编译型语言人类代码 ————》一次性把代码给 翻译官(编译器)————》汇编--》机器语言代表 : c,c,golang优点:执行速度快缺点:跨平台可移植性差硬件 , cpu , 有自己指令规则 0000000001 打印&#xf…

P1236-Network of Schools(学校网络)【最强联通块,Kosaraju】

正题 POJ题目链接 给出一个图&#xff0c;求联通块数量和加入多少条边后会将全图变为一个最强联通块。 机翻输入输出&#xff08;需要自取&#xff09; 输入 第一行包含整数N&#xff1a;网络中的学校数量&#xff08;2 < N < 100&#xff09;。学校由前N个正整数标识…

限时团购,6.9折:《微信开发深度解析:公众号、小程序高效开发秘籍》推荐序

全书由目 Senparc.Weixin SDK 作者苏震巍历时 2 年完成&#xff0c;涵盖了开发微信公众号及小程序需要用的的各项后端开发技能、技巧、避坑提示&#xff0c;以及 Senparc.Weixin SDK 微信公众号及小程序模块全面的使用说明及原理剖析。 Senparc.Weixin SDK 发布 4 年多来&#…

SSM框架知识点复习

第三节 SSM框架知识点复习 SpringMVC的知识 技能:使用SpringMVC来处理浏览器发起的请求。 ① SpringMVC的基本使用流程 i. 导入jar包 ii. 配置SpringMVC的配置文件 iii. 配置web.xml文件 iv. 创建控制器类并声明单元方法 ② SpringMVC的单元方法获取请求 i. 使用形参名和键名一…

某同学工作之后的感悟

本文原创&#xff1a;王梦茹以下是王梦茹同学在学习中和工作中的感悟。01学习中出来之后发现在学校的学习效率是远远比自主学习效率高的&#xff0c;学什么东西之前都会先知道他的大体概念&#xff0c;通透了解一下再学习会发现有效率的多。在有大致了解的情况下再去听老师讲课…

“半路出家”的程序猿怎么不被“熊”

文本原创&#xff1a;孙浩投稿这篇文章总有点‘好为人师’的感觉&#xff0c;但是自己作为一个“半路出家”的程序看过太多的和我同样出身的程序半途而废了。我现在在一家软件公司就职&#xff0c;担任技术主管职位。01 带过毕业生&#xff0c;也带过中途转行&#xff0c;也带过…

这可能是把Docker的概念讲的最清楚的一篇文章

转载自 这可能是把Docker的概念讲的最清楚的一篇文章 Docker 是世界领先的软件容器平台&#xff0c;本文主要来介绍下关于Docker的那些事儿&#xff0c;主要包含以下内容&#xff1a; 容器 什么是Docker&#xff1f; Docker思想、特点 Docker容器主要解决什么问题 容器 V…

【2018.3.17】模拟赛之一-ssl2574jzoj1368 无限序列【斐波那契数列】

正题 链接 需要纪中OJ账号 刚开始一个字符串”1”。然后进行无数次变化&#xff0c;1变为10,0变为1。然后求多个区间内的1的个数 输入输出&#xff08;需要自取&#xff09; Input   第一行为一个整数Q&#xff0c;后面有Q行&#xff0c;每行两个数用空格隔开的整数a, b。 …

Python变量名的定义规则与定义方式

变量名的定义规则 1.变量名只能是 字母、数字或者下划线的任意组合 2.变量名的第一个字符不能是数字 3.一下关键字不能声明为变量名 常用定义方式 驼峰法 AgeOfOld56 NumberOfStudents22 下划线 age_of_old56 第二种为官方推荐 定义变量不好的方式举例 1.变量名为中…

洋葱架构简介——分离是为了更好的结合

写出高质量软件是困难和复杂的&#xff1a;不仅仅是为了满足需求&#xff0c;还应该是健壮的&#xff0c;可维护的&#xff0c;可测试的&#xff0c;并且足够灵活以适应成长和变化。这就是洋葱架构出现的原因&#xff0c;它代表一组优秀的开发实践&#xff0c;用来开发任何的软…

RBAC(基于角色的权限访问控制)

第一节.RBAC简介 英文全称(Role-Based Access Control)中文全称:基于角色的权限访问控制rbac: 一种数据库设计思想,根据设计数据库设计方案,完成项目的权限控制.经常需要添加权限的情景 4.1 不同用户登录后看到的菜单是不一样的. 4.2 不同用户看到的页面效果不一样 4.2.1 有的…

子列表只是原列表的一个视图

原文参考&#xff1a;《编写高质量代码&#xff1a;改善java程序的151个建议》本文原创&#xff1a;穆雄雄上期文章&#xff1a;subList?? subString???上期我们说到&#xff0c;List接口提供了subList方法&#xff0c;其作用是返回一个列表的子列表。并且我们通过案例说明…

python注释的用法(单and多行)

单行注释 # name"asdfdasfdas"多行注释 """ print(xy) aad2"""快捷键 先选中要注释的内容然后Ctrl/

【2018.3.17】模拟赛之二-ssl1862jzoj1366 删数【区间dp】

正题 链接 需要纪中OJ账号 有n个数&#xff0c;可以选择删除一段区间&#xff0c;价值为|xi – xk|*(k-i1)。求删完所有数的最大价值 输入输出&#xff08;需要自取&#xff09; Input    输入文件的第一行为一个正整数N&#xff0c;第二行有N个用空格隔开的N个不同的正整…

ASP.NET Core之跨平台的实时性能监控

前言 前面我们聊了一下一个应用程序 应该监控的8个关键位置. 应用程序的8个关键性能指标以及测量方法 最后卖了个小关子,是关于如何监控ASP.NET Core的. 今天我们就来讲讲如何监控它,下面上效果图: 阅读本文需要了解的相关技术与内容: InfluxDb(分布式时序数据库,开源)(注…

EasyUI(前端框架)

第一节 EasyUI的介绍和常用组件 [1]EasyUI的介绍 介绍: EasyUI是一个前端开发的框架&#xff0c;其将常用的页面开发使用的组件进行了 封装&#xff0c;前端开发人员只需将EasyUI的资源导入项目后使用即可&#xff0c;快速 提升开发效率。 使用&#xff1a; ① 导入EasyUI的资…