IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 面试必备系列JUC(2) -- java内存模型(JMM)超详解 -> 正文阅读

[Java知识库]面试必备系列JUC(2) -- java内存模型(JMM)超详解

《笑傲江湖》里面,写了太多的尔虞我诈,欺骗陷害,仿佛这个江湖有无穷的黑暗和污浊。可在这个背景里,却永远有一个人,是没有被这个世界的种种,染下一丝污渍的。这个人心里,一直是最纯澈,最透明的,她就是仪琳。在这个大男人们为了权力、为了欲望,而无所不用其及的江湖中,只有她,心心念念的只是一个令狐大哥。 (秋叶掉落在树上)

仪琳:令狐大哥,我最近在看JUC相关的知识,可是对JMM的内容一知半解,不知令狐大哥今天能否给小妹讲讲?

令狐冲:仪琳师妹这就见外了,在介绍Java内存模型之前,咱们先来看一下到底什么是计算机内存模型,然后再来看Java内存模型在计算机内存模型的基础上做了哪些事情。

1.为什么要有计算机内存模型以及带来的问题

仪琳:那为什么要有计算机内存模型呢?

令狐冲:所谓内存模型,是与计算机硬件有关的一个概念。那么我先给你介绍下他和硬件到底有啥关系。

1.1 现代计算机内存模型

令狐冲:计算机在执行程序时,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入。由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这时就存在一个问题,由于CPU执行速度很快,而从内存读取数据和向内存写入数据的过程跟CPU执行指令的速度比起来要慢的多,因此如果任何时候对数据的操作都要通过和内存的交互来进行,会大大降低指令执行的速度。

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

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

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

令狐冲:是的,但是随着CPU能力的不断提升,一层缓存就慢慢的无法满足要求了,就逐渐的衍生出多级缓存。你看看下边这个图:

仪琳:这个图我知道,这是CPU的流行架构,当CPU要load一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。

?1.2?缓存一致性问题

仪琳:听说这里有一个面试常问缓存一致性问题,你能说说吗?

令狐冲:是的,我们分别来分析下单线程、多线程在单核CPU、多核CPU中的影响。一定要仔细听哦!

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

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

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

仪琳:这就是著名的缓存一致性问题吧!之前就听说在多线程场景下就可能存在缓存一致性问题也就是说,在多核CPU中,每个核的自己的缓存中,关于同一个数据的缓存内容可能不一致。

1.3 如何解决缓存一致性

令狐冲:那你知道如何解决缓存一致性问题吗?

仪琳:为了解决缓存不一致性问题,通常来说有以下2种解决方法:

? ? ? ? ? ? 1)通过在总线加LOCK锁的方式

 ? ? ?  2)通过缓存一致性协议

令狐冲:那为啥在总线上加LOCK锁可以解决呢?

仪琳:其实在早期的CPU当中,都是通过这种方式来做的。由于CPU和其他器件进行通信都是通过总线来进行的,如果对总线加LOCK锁的话,也就阻塞了其他CPU对其他部件访问(如内存),从而使得只能有一个CPU能使用这个变量的内存。只有当前的CPU完全执行完毕之后,其他CPU才能从变量i所在的内存读取变量,然后进行相应的操作。这样就解决了缓存不一致的问题。

令狐冲:但是上面的方式会有一个问题,由于在锁住总线期间,其他CPU无法访问内存,导致效率低下。后来是怎么做的呢?

仪琳:后来就出现了缓存一致性协议,最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。

仪琳:核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。

令狐冲:是的,在三级缓存架构中我们看到了,L3 cache和主存是共享的,所以就存在数据一致性的保障,MESI缓存一致性协议就作用在这里。MESI协议规定了CPU从主存(或者三级缓存)加载或者写入数据的规则,保证了数据的强一致性。

1.4 处理器优化和指令重排

令狐冲:除了缓存一致性问题之外,还有一种硬件问题也比较重要,那就是为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对输入代码进行优化排序,以方便执行处理,这就是处理器优化

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

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

?2. 并发编程中经典三问题

仪琳:前面说的和硬件有关的概念,我听得有点蒙,却依然不知道它到底和软件有啥关系?

令狐冲:其实前面说到的:缓存一致性问题、处理器优化乱序、指令重排问题,就是软件并发编程中的三个经典问题:原子性问题,可见性问题,有序性问题。

仪琳:这又是什么?

2.1 原子性

令狐冲:先说下原子性,即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。(要么全部执行成功,要么全部失败)

仪琳:听不太懂,你能举个实际的例子吗?

令狐冲:举个最简单的例子,你想一下假如为一个32位的变量赋值过程不具备原子性的话,会发生什么后果?

//example	
i = 9;

令狐冲: 假设一个线程执行到这条代码,我在这里假设为一个32位的变量赋值包括两个过程:为低16位赋值,为高16位赋值。

仪琳:那么就可能发生一种情况:当将低16位数值写入之后,突然被中断,而此时又有一个线程去读取i变量的值,那么读取到的就是错误的数据。这就无法保证原子性了。

令狐冲:没错!记住这个例子。

2.2?可见性

仪琳:可见性呢?是什么?

令狐冲:可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。我举个简单的例子:

//线程1执行的代码
int i = 0;
i = 10;
 
//线程2执行的代码
j = i;

令狐冲:假若执行线程1的是CPU1,执行线程2的是CPU2。由上面的分析可知,当线程1执行 i =10这句时,会先把i的初始值加载到CPU1的高速缓存中,然后赋值为10,那么在CPU1的高速缓存当中i的值变为10了,但是却没有立即写入到主存当中。

令狐冲:此时线程2执行 j = i,它会先去主存读取i的值并加载到CPU2的缓存当中,注意此时内存当中i的值还是0,那么就会使得j的值为0,而不是10.

令狐冲:这就是可见性问题,线程1对变量i修改了之后,线程2没有立即看到线程1修改的值。

2.3 有序性

仪琳:我懂了,那什么是有序性呢?

令狐冲:有序性:即程序执行的顺序按照代码的先后顺序执行。这个有点复杂,看下面这个例子吧:

int i = 0;              
boolean flag = false;
i = 1;                //语句1  
flag = true;          //语句2

仪琳:从代码顺序上看,语句1是在语句2前面的,那JVM在真正执行这段代码的时候会保证语句1一定会在语句2前面执行吗?

令狐冲:不一定,为什么呢?这里可能会发生指令重排序(Instruction Reorder)

仪琳:指令重排序我从书上看过,就是处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

令狐冲:没错,比如上面的代码中,语句1和语句2谁先执行对最终的程序结果并没有影响,那么就有可能在执行过程中,语句2先执行而语句1后执行。

仪琳:指令重排序会保证程序最终结果会和代码顺序执行结果相同,那么它靠什么保证的呢?

令狐冲:我给你举个例子吧。

int m = 100;    //语句1
int n = 5;    //语句2
m = m + 3;    //语句3
n = m*m;     //语句4

令狐冲:这段代码有4个语句,其中一个可能的执行顺序是:

????????语句2---》 语句1---》 语句3---》 语句4

仪琳:那可不能是下边这个执行顺序呢?

????????语句3---》 语句1---》 语句2---》 语句4

令狐冲:这个不可能,因为处理器在进行重排序时是会考虑指令之间的数据依赖性。如果一个指令Ins?2必须用到Ins?1的结果,那么处理器会保证Ins 1会在Ins?2之前执行。

仪琳:那么说,重排序不会影响单个线程内程序执行的结果,但是多线程呢?

令狐冲:好问题,还是先看个例子吧。

//线程1:
conf = getconf();   //语句1
begin = true;       //语句2
 
//线程2:
while(!begin ){
  sleep()
}
doSomething(conf);

令狐冲:上面代码中,由于语句1和语句2没有数据依赖性,因此可能会被重排序。假如发生了重排序,在线程1执行过程中先执行语句2,而此是线程2会以为初始化工作已经完成,那么就会跳出while循环,去执行doSomething(conf)方法,而此时conf并没有被初始化,就会导致程序出错。

仪琳:所以,指令重排序不会影响单个线程的执行,但是会影响到线程并发执行的正确性

令狐冲:是的,如果要想并发程序正确地执行,必须要保证原子性、可见性以及有序性,且缺一不可。

3. java内存模型

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

令狐冲:Java内存模型(Java Memory Model)即JMM是一个抽象的概念,JMM是一个抽象的概念,并不是物理上的内存划分,一定要明白!

令狐冲:Java内存模型(JMM)定义了Java虚拟机(JVM)在计算机内存(RAM)中的工作规范。在硬件内存模型中,各种CPU架构的实现是不尽相同的,Java作为跨平台的语言,为了屏蔽底层硬件差异,定义了Java内存模型(JMM)。JMM作用于JVM和底层硬件之间,屏蔽了下游不同硬件模型带来的差异,为上游开发者提供了统一的使用接口。

仪琳:其实你说了这么多其实就是想说明白JMM——JVM——硬件的关系。总之一句话,JMM是JVM的内存使用规范,是一个抽象的概念。

令狐冲:在JMM中,内存划分为两个区域,线程本地内存,主内存。

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

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

仪琳:那么Java语言本身对原子性、可见性以及有序性提供了哪些保证呢?

3.1 java如何实现原子性

令狐冲:在Java中,为了保证原子性,提供了两个高级的字节码指令monitorentermonitorexit。这两个字节码,在Java中对应的关键字就是synchronized当然,也可以用Lock来实现,synchronized和Lock能够保证任一时刻只有一个线程执行该代码块,那么自然就不存在原子性问题了,从而保证了原子性。synchronized这个之后的文章会有讲。

3.2java如何实现可见性

仪琳:java如何实现可见性的呢?

令狐冲:对于可见性,Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

令狐冲:另外,通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。

3.3 java如何实现有序性

仪琳:那java如何实现有序性的呢?
令狐冲:在Java中,可以使用synchronizedvolatile来保证多线程之间操作的有序性。实现方式有所区别:

volatile关键字会禁止指令重排。

synchronized关键字保证同一时刻只允许一条线程操作。

仪琳:我发现,好像synchronized关键字是万能的,他可以同时满足以上三种特性。

令狐冲:这其实也是很多人滥用synchronized的原因。但是synchronized是比较影响性能的,虽然编译器提供了很多锁优化技术,但是也不建议过度使用。

令狐冲:悄悄的告诉你一个比较重要的小秘密,其实,Java内存模型具备一些先天的“有序性”,不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。

仪琳:那令狐大哥,赶快说说吧!

? 3.4 happen-before原则

令狐冲:happens-before原则,也称为先行发生原则,一共有8条

  • 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作
  • 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作
  • volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  • 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  • 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作
  • 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  • 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行
  • 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

仪琳:多谢令狐大哥,我已经完全get了,了解了什么是Java内存模型、Java内存模型的作用以及Java中内存模型做了什么事情等。关于Java中这些和内存模型有关的关键字,希望以后还可以继续深入学习。

令狐冲:那你要多看后面的文章分享呀。

4 面试常问问题

1.什么是java的内存模型

2.java内存模型有什么作用吗

3.知道缓存一致性协议吗

4.知道啥是happen-before吗

5. 如何解决缓存一致性问题的,有几种方法?

===================================================
字节内推:
字节内推〉字节校招开启。简历砸过来!!!!!!!
200多个岗位,地点:北京 上海 广州 杭州 成都 深圳。。

字节师兄内推码:B1RHWFK
官网校招简历投递通道:https://jobs.toutiao.com/campus/m/position?referral_code=B1RHWFK

===================================================
微信公众号:猿侠令狐冲

公众号定期更新文章!!

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-19 11:55:18  更:2021-08-19 11:56:02 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 9:08:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码