俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下:

  • 为什么学习Java的内存模式
  • 缓存一致性问题
  • 什么是内存模型
  • JMM(Java Memory Model)简介
  • volatitle关键字
  • 原子性
  • 可见性
  • 有序性
  • 指令重排
  • 先行发生——happen-before原则
  • 解释执行和编译执行
  • 其他语言(c和c++)也有内存模型么?

 


  为什么需要关注Java内存模型?

 
  之前有一个我实习的同事(已经工作的)反讽我:学(关注)这个有什么用?
  我没有回答,我牢记一句话:大天苍苍兮大地茫茫,人各有志兮何可思量。我只知道并发程序的bug非常难找。它们常常不会在测试中发现,而是直到程序运行在高负荷的情况下或者长期运行之后才发生,但是那时候再修复的代价是很大的,且也非常难于重现和跟踪。故开发,维护人员需要花费比之前更多的努力,去提前保证程序是正确同步的。而这不容易,但是它比前者——调试一个没有正确同步的程序要容易的多。
  本文肯定不会,也不可能全面深入的总结完每个Java内存模型的知识点,只是作为熟悉JVM的内存模型,而内部的一些具体的原理和细节,之后开专题总结之。
 
  缓存一致性问题

  众所周知,计算机某个运算的完成不仅仅依靠cpu及其寄存器,还要和内存交互!cpu需要读取内存中的运行数据,存储运算结果到内存中……其中很自然的也是无法避免的就涉及到了I/O操作,而常识告诉我们,I/O操作和cpu的运算速度比起来,简直没得比!前者远远慢于后者(书上说相差几个数量级!),前面JVM学习2也总结了这个情景,人们解决的方案是加缓存——cache(高速缓存),cache的读写速度尽可能的接近cpu运算速度,来作为内存和cpu之间的缓冲!旧的问题解决了,但是引发了新的问题!如果有多个cpu怎么办?

  现代操作系统都是多核心了,如果多个cpu和一块内存进行交互,那么每个cpu都有自己的高速缓存块……咋办?也就是说,多个cpu的运算都访问了同一块内存块的话,可能导致各个cpu的缓存数据不一致!if发生了上述情景,then以哪个cpu的缓存为主呢?为了解决这个问题,人们想到,让各个cpu在访问缓存时都遵循某事先些规定的协议!因为无规矩不成方圆!如图(现在可以回答什么是内存模型了):

JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html

  什么是内存模型?

  通俗的说,就是在某些事先规定的访问协议约束下,计算机处理器对内存或者高速缓存的访问过程的一种抽象!这是物理机下的东西,其实对虚拟机来说(JVM),道理是一样的!

 

  什么是Java的内存模型(JMM)?

  教科书这样写的:JVM规范说,Java程序在各个os平台下必须实现一次编译,到处运行的效果!故JVM规范定义了一个模型来屏蔽掉各类硬件和os之间内存访问的差异(比如Java的并发程序必须在不同的os下运行效果是一致的)!这个模型就是Java的内存模型!简称JMM。

  让我通俗的说:Java内存模型定义了把JVM中的变量存储到内存和从内存中读取出变量的访问规则,这里的变量不算Java栈内的局部变量,因为Java栈是线程私有的,不存在共享问题。细节上讲,JVM中有一块主内存(不是完全对应物理机主内存的那个概念,这里说的JVM的主内存是JVM的一部分,它主要对应Java堆中的对象实例及其相关信息的存储部分)存储了Java的所有变量。且Java的每一个线程都有一个工作内存(对应Java栈),里面存放了JVM主内存中变量的值的拷贝!且Java线程的工作内存和JVM的主内存独立!如图:

JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html

  当数据从JVM的主内存复制一份拷贝到Java线程的工作内存存储时,必须出现两个动作:

  1. 由JVM主内存执行的读(read)操作
  2. 由Java线程的工作内存执行相应的load操作

  反过来,当数据从线程的工作内存拷贝到JVM的主内存时,也出现两个操作:

  1. 由Java线程的工作内存执行的存储(store)操作;
  2. 由JVM主内存执行的相应的写(write)操作

  read,load,store,write的操作都是原子的,即执行期间不会被中断!但是各个原子操作之间可能会发生中断对于普通变量,如果一个线程中那份JVM主内存变量值的拷贝更新了,并不能马上反应在其他变量中,因为Java的每个线程都私有一个工作内存,里面存储了该条线程需要用到的JVM主内存中的变量拷贝!(比如实例的字段信息,类型的静态变量,数组,对象……)如图:

JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html

A,B两条线程直接读or写的都是线程的工作内存!而A、B使用的数据从各自的工作内存传递到同一块JVM主内存的这个过程是有时差的,或者说是有隔离的!通俗的说他们之间看不见!也就是之前说的一个线程中的变量被修改了,是无法立即让其他线程看见的!如果需要在其他线程中立即可见,需要使用 volatile 关键字。现在引出volatile关键字:

 


 

  volatile 关键字是干嘛的?举例说明。

  前面说了,各个线程之间的变量更新,如果想让其他线程立即可见,那么需要使用它,故volatile字段是用于线程间通讯的特殊字段。每次读volatile字段都会看到其它线程写入该字段的最新值!也就是说,一旦一个共享变量(成员、静态)被volatile修饰,那么就意味着:a线程修改了该变量的值,则这个新的值对其他线程来说,是立即可见的!先看一个例子:

  这段代码会完全运行正确么?即一定会中断么?

 

JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html
//线程A
boolean stop = false;

while(!stop){
    doSomething();
}
 
//=========
//线程B
stop = true;
JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html

 

  有些人在写程序时,如果需要中断线程,可能都会采用这种办法。但是这样做是有bug的!虽然这个可能性很小,但是只要一旦bug发生,后果很严重!前面已经说了,Java的每个线程在运行过程中都有自己的工作内存,且Java的并发模型采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明,这也是为什么如果编写多线程程序的Java程序员不理解隐式进行的线程之间通信的工作机制,则很可能会遇到各种奇怪的并发问题的原因。针对本题的A、B线程,如果他们之间通信,画成图是这样的:

JVM学习(3)——总结Java内存模型---转载自http://www.cnblogs.com/kubixuesheng/p/5202556.html

那么线程A和B需要通信的时候,第一步A线程会将本地工作内存中的stop变量的值刷新到JVM主内存中,主内存的stop变量=false,第二步,线程B再去主内存中读取stop的拷贝,临时存储在B,此时B中工作内存的stop也为false了。当线程B更改了stop变量的值为true之后,同样也需要做类似线程A那样的工作……但是此时此刻,恰恰B还没来得及把更新之后的stop写入主存当中(前面说了各个原子操作之间可以中断),就转去做其他事情了,那么线程A由于不知道线程B对stop变量的更改,因此还会一直循环下去。这就是死循环的潜在bug!

  从整体来看,这两个步骤实质上是线程A在向线程B发送消息,而且这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的工作内存之间的交互,来为java程序员提供内存可见性保证。但是它们之间不是立即可见的

  如果stop使用了volatile修饰,会使得:

  • B线程更新stop值为true,会强制将修改后的值立即写入JVM主内存,不许原子操作之间中断。
  • 线程B修改stop时,也会让线程A的工作内存中的stop缓存行失效!因为A线程的工作内存中JVM主内存的stop的拷贝值缓存行无效了,所以A线程再次读取stop的值会去JVM主内存读取

这样A得到的就是最新的正确的stop值——true。程序完美的实现了中断。很多人还认为,volatile这么好,它比锁的性能好多了!其实这不是绝对的,很片面,只能说volatile比重量级的锁(Java中线程是映射到操作系统的原生线程上的,如果要唤醒或者是阻塞一条线程需要操作系统的帮忙,这就需要从用户态转换到核心态,而状态转换需要相当长的时间……所以说syncronized关键字是java中比较重量级的操作)性能好,而且valatile万万不能代替锁,因为它不是线程安全的,既volatile修饰符无法保证对变量的任何操作都是原子的!(鉴于主要涉及了Java的并发编程,之后再开专题总结)。

  

  什么是原子性?

  在Java中,对基本数据类型的变量的操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。看例子:

1 int x = 10;         //语句1
2 y = x;                //语句2
3 x++;                 //语句3
4 x = x + 1;         //语句4

相关文章:

  • 2021-07-01
  • 2021-10-27
  • 2021-12-06
  • 2022-03-03
  • 2021-06-26
  • 2021-10-25
  • 2022-12-23
  • 2021-11-23
猜你喜欢
  • 2022-02-12
  • 2021-08-07
  • 2021-09-13
  • 2021-07-15
  • 2022-02-21
  • 2021-07-29
相关资源
相似解决方案