首页 > 编程语言 > 详细

深入java内存模型(一)

时间:2015-11-09 22:30:13      阅读:259      评论:0      收藏:0      [点我收藏+]

  最近本来想深入学习一下java线程,很想知道其中实现的原理,比如线程资源的共享,线程私有空间,以及线程直接的同步控制等。如果能了解它的实现,对于深入学习线程,会有很大的帮助。最近正在看一份《深入java内存模型》的资料。讲的就是java线程方面的实现原理,拿出来分享一下。

  说到线程,我们首先想到的是线程的通信。学习操作系统时,线程通信有两种,一种是通过共享内存,另一种是通过消息传递。共享内存属于隐式的通信,线程之间共享一块内存,线程都可以往这块内存写数据,读数据就实现了通信。而消息传递时一个显示的过程,你想往另一个线程通信,你必须向它发送消息,另一个线程接受消息。这样也能实现一个通信。线程中有一个重要的功能就是同步。对于共享内存,由于他们的通信是隐式的,那么要实现同步,则必须显式的标明同步动作。而对于消息传递,发送肯定是在接受前面,属于隐式。那么java的线程到底属于那一种呢? 如果我们对java的内存种类有所了解的话,应该知道用的是共享内存模式。

  我们以线程的角度来分析一下java的内存分类。最常见的,也是我们使用最多的就是栈以及堆。简单说栈属于java线程的私有内存,线程之间不能访问。而堆则属于共享内存,线程之间通过堆可以共享数据。如下图所示:

技术分享技术分享
这个图说明了java线程与java内存模型的关系。
 
  重排序:
    重排序这个是很多地方都有用到。比如处理器对指令的处理,数据库系统对并发数据的处理等。在执行程序时候,java编译器和处理器会对指令进行重排序,比如,一连串动作,1,读取A,2,读取B,3,写入A,4,写入B.那么处理的时候,可能会变成,1,3,2,4的顺序,我们知道这样执行和原顺序执行的结果是一样的,但是这样效率会更高。java的各种编译器都有自己的重排序功能,使效率最大化。对于单线程来说,重排序并不会有什么问题。但对于多线程来说,这个排序就很有可能会扰乱程序的结果。
  重排序包括三种:
    一是java编译器对执行顺序的一个重排序。
    二是处理器对指令执行顺序的一个重排序。
    三是内存系统,由于使用了缓存和读写缓存,这样加载和执行可能会使乱序执行。
  最终的结果是引起执行指令的重排序。
 
  我们通过下面一段代码来说明:
  代码1:{
       int a = 1;  //步骤A1
       int y = b; //步骤A2
      }
  代码2:{
       int b = 1;//步骤B1
       int x = a;//步骤B2
      }
  两个线程分别执行代码1,代码2.我们预期的结果是y= 1,x = 2.但是实际情况有可能会使x=y=0;如果对于重排序不了解,那么怎么想都不会出现这个结果。那么重排序是怎么做的呢。因为代码中两个步骤都是不相关的,那么执行顺序可以为A2,B2,A1,B1这样执行的话那么a,b初始值都为0,自然x,y都为0了。从这我们可以看出来,重排序功能使得我们不知道最终结果会怎样,如果就这样肯定是不合理。 
  jvm对这个当然有一定的规范。首先重排序是不应改变执行结果的。首先对于数据依赖的不能排序。什么是数据依赖呢。
  比如{
      y = a
      a = 5
    }
  这两个的顺序是不能改变的,他们之间依赖a的数据,改变会影响结果。
  重排序符合数据依赖原则,如果他按照代码写的顺序执行,就一定没问题了么。 同样刚才那个问题。执行顺序A1,A2. B1,B2的执行顺序不变。还是有可能会得到x= y= 0的情况。至于什么原因呢。这和java的线程模型有关。java线程有私有内存和共享内存之分。步骤A1中,写入a= 1,首先做的只是吧1写入到私有内存的临时变量中,放入写缓冲。同时执行第二步,读取b。代码2中也是这样逻辑,那么b=2其实还没写到共享内存中,还在写缓冲中。此时读到的共享内存的b还是0,然后执行写缓冲写入共享内存。过程如下:
技术分享技术分享
对于这种情况,jvm有一个内存屏障的机制能有效的控制程序按照自己控制的执行。内存屏障就是控制数据加载与写入的顺序。
jvm的内存屏障有以下四种:
 1,LoadLoad, 如 load1 LoadLoad load2,确保 load1数据的加载在load2及以后的加载指令之前
 2,StoreStore,如 store1 StoreStore store2,确保store1数据对其他处理器及线程可见(刷新到内存)之前于store2及以后的存储指令。
 3,LoadStore,如 load1 LoadStore store1 确保load1数据的加载之前于store1及以后的存储刷新到内存。
 4,StoreLoad 如 store1 StoreLoad load1 确保store1数据对其他处理器其他线程可见(刷新到内存)之前于load1及以后的数据加载指令。同时会使屏障之前的内存指令(加载与存储)执行完成之后,才会执行屏障后的内存访问指令。
 
jvm有这四个内存屏障能有效的对重排序进行控制。但这个理解与使用起来比较麻烦。jdk1.5以后使用的内存模型,用一个happen-before的概率来阐述操作直接的内存可见性。使之不需要关注内存屏障方面的事情。
主要的happen-before规则有以下几种:
  1,对于单一线程:中间的每个操作,都happen-before后续的每个操作(对于这个,并不是执行的先后顺序,指的是执行的可见结果。如果两条指令都不相关。那么执行的可见结果也就一样,就不一定会按照执行顺序来)
  2, 监视器所操作:监视器的解锁都happen-before于随后对这个监视器的加锁。
  3,对于volatile变量,写happen-before与后续的读。(这里的写和读,指的都是对共享内存,而不是私有内存)
  4,传递性,如果A happen-before B,B happen-before C,那么A happen-before C.
  对于happen-before于jvm以及重排序直接的关系如下图:
技术分享
 
以上是对java内存模型的一个整体介绍,希望能通过后续的学习加深对java内存模型的理解。
 

深入java内存模型(一)

原文:http://www.cnblogs.com/2015zzh/p/4951278.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!