JAVA内存模式-1-基础知识

并发编程

并发编程的2个关键问题:

①线程之间如何通信:共享内存、消息传递。

②线程之间如何同步:基于共享内存的同步必须基于程序员显式控制、基于消息传递的同步则是隐式传递。

JAVA是共享内存模型,通过对存储在“堆内存”中的变量进行读取-设置来实现通信。“栈内存”是线程私有的,不存在共享这一说。

重排序

为了提高被执行程序的性能,所以,编译器和处理器会对指令做重排序。重排序分3类:

①编译器优化的重排序。编译器在不改变单线程语义的前提下优化程序的执行顺序。

②指令级别的并行重排序。计算机CPU通过指令级别的并行技术来对多条指令来重叠执行,前提是不存在数据依赖的前提下。(因为存在依赖的话,那就必须分前后串行而不是并行)

③内存系统的重排序。由于CPU使用缓存、读写缓冲区,这就导致加载和操作内存好像是毫无头绪的执行,其实不是的。

问题1:你怎么确保CPU和内存能正确的重排序?

JAVA编译器的重排序,属于JAVA管辖,这我没什么问题,问题是JAVA是怎么控制的CPU和内存的重排序呢?毕竟人家跟JAVA是两码事??????

①针对编译器重排序,JMM会禁止某些类型的编译器重排序。

②针对CPU重排序,JMM通过插入特定类型的内存屏障指令来禁止某些类型的CPU重排序。(好牛掰啊。不过话又说回来,你CPU也是解析指令来运行的,只要我插入特定指令就行,至于哪个程序插入的,这CPU就管不着了)

③针对内存重排序,没什么特殊要求,上面2个控制好了,这个也就跟着控制好了,不需要额外的操作。

问题2:CPU重排序与内存屏障指令,什么关系?

现在CPU有很多核,比如:Intel的I7,有8核版本;

每一核都有属于自己所独有的写缓冲区,其他核无法访问;

写缓冲区?什么鬼?写缓冲区是用来临时保存写入内存的数据,这样CPU就不用因为数据写入内存而等待该操作的完成,这样CPU就可以像流水一般的去执行。同时写缓冲区通过批处理不同内存地址、合并对同一内存地址的写操作等方式来减少对内存总线的占用。

那么,问题来了,由于其他核不能访问某核的写缓冲区,那其他核怎么知道该什么时候去访问主内存、某某核什么时候会把写缓冲区的数据刷新到主内存中去呢?这就导致严重的问题:CPU处理器读取内存时间、内存写入/刷新的时间,可能存在乱序!!!

为了让其他核的CPU能看到某一个具体核的内存缓冲区,JAVA编译器在生成指令序列的适当位置会插入内存屏蔽指令来禁止特定类型的CPU重排序(也是,不懂业务逻辑就别瞎重排序,出事了程序员倒霉了),JMM的内存屏蔽指令分为4类:

屏障类型

指令示例

说明

LoadLoad

Load1;LoadLoad;Load2

CPU优先加载Load1,

其次加载Load2及其之后指令

StoreStore

Store1;StoreStore;Store2

优先把Store1写入主内存

其次再把Store2及后续指令写入到主内存

LoadStore

Load1;LoadStore;Store2

CPU优先加载Load1指令

其次再把Store2及后续指令写入主内存

StoreLoad

Store1;StoreLoad;Load2

优先把Store1写入主内存

其次CPU再加载Load2及后续指令

 

happens-before

JSR-133对happens-before规定:如果一个操作的结果需要对另一个操作可见,即这两个操作必须存在happens-before关系,而这两个操作可以存在于不同的线程,也可以是同一个线程。(不同线程可见?那就是要求CPU某核必须、尽快把数据从写缓冲区刷新到主内存,这样其他核才能看到)

要实现happens-before,基本可以从下面下手:

1.程序顺序规则:那就A操作代码的位置要由于B操作代码的位置。(通俗点就是:写代码的时候,想要可见,那就把可见的代码位置提前,就像使用变量之前必须先定义变量)

2.监视器锁规则:加锁来确保A代码首先执行呗(第一条针对的同线程,这个针对不同线程)

3.volatile变量规则:volatile修饰的变量,先使用肯定先刷新到主内存。

4.传递性:如果A happens-before B且B happens-before C,那么A happens-before C。

这里有点绕口:两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行,而是仅仅要求前一个操作的执行结果对后一个操作可见,且前一个操作按顺序排个第二个操作之前。

个人理解:假如A happens-before B,按照我们写代码的习惯,A肯定写在B的前面,因此从顺序上来讲前一个操作在第二个操作之前。

但是吧,A线程不一定优于B线程先执行,可能是这种情况:

B线程先执行,时间到了之后,B线程挂起且还没执行需要A线程结果的那行代码。

A线程后执行,但在时间到之前已经执行完毕。

B线程从挂起状态转入执行状态,然后拿到A线程的结果,因此,对于B线程而言,谁先执行无所谓,只要能确保B需要A结果时能拿到正确的结果就行。

要想达到这种效果,必须需要JMM和CPU的共同努才能实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值