`
gaoyuntao2005
  • 浏览: 303021 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

java并发学习之五:读JSR133笔记(持续更新中)

阅读更多

在写线程池的时候,遇到了很多的问题,特别是happen-before应该怎么去理解,怎么去利用,还有reorder,哪些操作有可能会被reorder?在这一点上,发现其实《concurrent in practice》也没描述得太清晰。 

在网上搜了一遍,发现JSR133的faq相对而言,还算稍微解释了一下,发现JSR133其实也就40多页,所以也就顺带看了一遍,因为大部分的内容都比较简单(越往后看发现越复杂~),但是里面的定义比较难理解,所以只记录了定义和一些个人认为比较重要的地方,或者比较难理解的地方 
这里是阅读笔记,以备以后查阅 


前言部分 

1.下面的网站提供了附加的信息,帮助进一步地理解JSR133 
http://www.cs.umd.edu/~pugh/java/memoryModel/ 

2.在JLS中很可能需要JVM(TM)实现的两个原始的定义的改变: 

  • volatile变量的语义被加强了,以前的语义是允许自由地被reorder的
  • final的语义也被加强了,现在可以不需要显性得同步,就可以获得线程安全的不变性。这可能需要在含有设置final field的构造函数的结尾,加入一些存储屏障的步骤


一.introduction 

3.JSR133并不是描述多线程程序应该怎么去执行,而是描述多线程程序允许怎么去显示的,他包括一些规则,这些规则定义了一个被多线程更新的共享变量的值,是否是可见的。(比如读一个共享变量的值,根据规则,应该显示的是什么) 

4.还是synchronized的问题,该问题在之前的文章中也提到过,在方法上的时候,它锁的是this,如果是静态方法,那么锁的是方法所在类的class 

二.Incorrenctly Synchronized Programs Exhibit Surprising Behaviors 

5.不合适的同步(improperly synchronized):(注:并不意味着错误)
  • 一个线程写
  • 另一个线程读
  • 读和写没有用synchronized来保证顺序
当发生这些,我们就说有数据竞争(data race),包含有数据竞争的代码,可能会出现一些违反直觉的结果。 

三.Informal Semantics 

6.正确的同步(correct synchronization)(严格地保证多线程访问的正确性,但吞吐率很低的) 
理解一个程序是否被正确地同步,有两个关键点: 
  • 冲突的访问(Conflicting Accesses):对同一个共享域或共享数组的多个访问,并且这些访问至少有一个是写,就说明有冲突。
  • Happens-Before关系:如果一个动作happens-before另一个,前者对后者是可见的,并且在执行顺序上也会在后者的前面。
  • 这点必须强调一下:一个happens-before关系,并不是暗示这些动作在java平台实现中,必须按这样的顺序去执行。(这里并不是很理解,难道也会是一个幻象?)原话是这样的:It should be stressed that a happens-before relationship between two actions does not imply that those actions must occur in that order in a Java platform implementation. 
    Happens-before关系最主要是强调并定义了两个有竞争的动作之间的顺序(当数据竞争出现时) 
    happens-before的规则包括:(注意,这些动作在直觉上是本该如此的,但在多线程中,就不一定了,所以才有这些规则) 
    a.在这个线程中,每个动作happens-before每个之后的动作。 
    b.一个对固有锁(monitor)的unlock操作happens-before每个之后的对monitor的lock操作。 
    c.一个对volatile filed的写happens-before每个之后的对该volatile的读(注意,这里没有线程的限制) 
    d.一个对线程的start()的调用(call)happens-before该线程中任何动作。 
    e.在线程中的所有动作happens-before任何其他线程成功地调用对该线程的join()返回 
    f.如果a happens-before b,b happens-before c,那么a happens-before c,即具有传递性 
happens-before在第五章会更详细彻底地定义。 
这里有一个很有意思的图片,可能对别人没太多的意义,但对我自己而言,真是解释了不少以前的迷惑,最主要是第二幅图片的,之前有一个误解:数据只有在同步块中,而另一个线程必须也进入一个相同的锁的同步块中,才能保证其可见性。但其实不是这样的,只要能够保证顺序一致性,就是可见的,如图a 
 

7.final field 
在这一节,并没有解释final field是怎么保证其同步的(根据之前的理解,如果有final field,应该在对象的构造完成之后做了一些事来保证同步,但到底做了什么事?还得看下文第9章),只是定义了final field在构造完成后,就不会再改变,所以只需要在构造器中,保证其没有escape,就可以正确地在并发环境中无限制地使用了。 

四.What is a Memory Model? 

8.存储模型(memory model)的定义: 
一个存储模型(memory model)描述的是,给定一个程序和这个程序的执行轨迹(trace),就可以判断这个执行轨迹(trace)是否是合法的。在Java程序语言中,存储模型(memory model)是这样工作的:检查在运行轨迹(trace)中的每个读,并且根据一定的规则,校验这个读观察到的写是否是有效的。 
存储模型(memory model)描述一个程序可能的举止。一个存储模型(memory model)实现可以随意地产生任何的代码(像reorder,删除一些没必要的同步),只要所有的程序运行结果可以根据存储模型(memory model)来预测。 

9.JVM做的一些事 
当我们说读(read),我们只是说的是一些这样的动作:如读一些fields或者数组。其他操作的语义,像读一个数组的长度,执行一个类型转换(checked casts),或者调用一个虚拟的方法(invocations of virtual methodds,个人的理解应该是调用接口的一个方法),是不会直接受数据竞争的影响的。JVM的实现保证了数据竞争不会导致错误的举动,像返回一个错误的数组长度,或者调用虚拟方法的错误(在竞争的数据中,应该是可能发生的)。 

五.Definitions 

10.共享变量/堆内存(Shared variables/Heap memory): 
能够在线程间被共享的内存被叫做共享内存或者堆内存。所有的实例域(instance fields),静态域(static fileds)和数组都被存在堆内存。我们用变量(variable)来引用所有的域和数组。在一个方法中的本地变量不会在线程中共享,也不会受存储模型(memory model)影响。 

11.线程交互动作(Inter-thread Actions): 
一个线程交互动作(inter-thread action)是这样的动作:它在一个线程中被调用,可以被其他线程检测或者直接影响。inter-thread actions包括读和写共享变量和同步动作,像lock或者unlock,读或者写一个volatile变量,和开始一个线程。同时,也包括一些与外部世界交互的动作(external actions),和可以导致一个线程进入无限循环的动作(thread divergence actions)。 
每一个线程交互动作(inter-thread action)都是与这个动作相关的信息联系在一起的。所有的动作都与它被调用的线程联系在一起,并且和线程中的程序顺序(Program order)联系在一起。附加的联系信息包括: 
write The variable written to and the value written. 
read The variable read and the write seen (from this, we can determine 
the value seen). 
lock The monitor which is locked. 
unlock The monitor which is unlocked. 

12.程序顺序(Program order): 
根据线程内语义(intra-thread semantics),包括所有的线程t中的线程交互动作(inter-hread actions),和所有的动作在内,t的程序顺序(program order)是唯一影响执行顺序的。(这个定义应该是说在线程中,表现出来的执行顺序永远是一致的,即使某些动作与其他线程有交互) 

13.线程内语义(Intra-thread semantics): 
线程内语义(Intra-thread semantics)是一个单线程程序的基本语义,它允许根据在线程中读操作看到的值对线程的动作的完整的预测。为了确定线程中的动作是否是合法的,我们只是简单地评估在单线程中它的正确性,像定义在JLS中的。 
每次线程t的评估产生一个线程交互动作(inter-thread action),它必须匹配这个程序顺序中的下一个动作a。如果a是一个读操作,那么进一步的评估会根据存储模型(memory model)并使用看到的a的值做决定。 
简单地说:线程内语义(intra-thread semantics)决定了在一个单独的线程中的执行。当值是从堆里面读出来的,他们就由存储模型(memory model)来决定 

14.同步动作(Synchronization Actions): 
同步动作包括lock,unlock,对volatile变量的读和写,开始一个线程的动作start,检测一个线程是否结束的动作join等。任何在一个synchronizes-with边缘(edge),包括开始和结束点,都是一个同步动作(synchronization action)。这样的动作会在后面的happens-before边缘(edge)更详细地列出来 

15.同步顺序(Synchronization order): 
每个执行有一个同步顺序(怎么理解?)。一个同步顺序是包括所有的在该执行中的同步动作的所有顺序。(也不是太理解) 

16.Happens-Before and Synchronizes-With Edges: 
同步动作(synchronized action)也会引发happen-before边缘,我们将结果指向边缘(directed edges)叫做synchroized-with edges。他们定义在下面: 
  • 一个unlock动作synchronizes-with所有之后的在同一个锁上的lock动作(这里“之后的”的含义根据synchronization order定义)
  • 一个对volatile变量的写synchronizes-with所有之后的对该变量的读操作(任何线程)(这里“之后的”的含义根据synchronization order定义)
  • 一个开始一个线程的动作synchronizes-with线程开始后的第一个操作
  • 线程T1的最后一个动作synchronizes-with另一个线程T2检测到T1已经停止后的任何动作。T2可以通过调用T1.isAlive()或者join动作来完成这个操作。
  • 如果线程T1中断(interrupts)线程T2,T1的interrupt动作synchronizes-with任何其他的线程(包括T2)检测到T2被中断interrupted。
  • 对每个变量的默认值的写synchronizes-with每个线程的第一个动作。
  • 对一个对象的finalizer的调用,有一个隐性的对该对象引用的读操作。在对象的构造器的结尾和这个读操作之间,有一个happens-before edge。
  • 注意:所有的对这个对象的冻结(freezes)happen-before这个happens-before edge的开始点。(这段比较难理解,在9.2会重新提及,暂时先记录下来) 

17.happen-before规则的稍微详细的解释: 
注意这一点:两个动作间happens-before关系的存在,并不意味着他们在实现上也按这个顺序来执行。如果一个重排序产生的结果与合法的操作一致,它就不是非法的。举个例子:对一个对象中所有变量(field)默认值的写操作并不一定要发生在线程开始后的动作之前,只要没有任何的读操作观察到这个事实。(也就是说,如果没有操作去读,即使有happen-before关系,也是允许重排序的,这其实也解释了为什么我们在其他线程中能观察到该线程的乱序操作,因为在本线程中虽然有重排序操作,但是没有读操作,所以允许它重排序) 
进一步说,如果两个操作享有一个happens-before的关系,另一个没有享有该happen-before关系的动作,不一定能观察到他们这个happen-before的顺序,有可能仍然是乱序的。 

六.Approximations to a Java Memory Model 

这一章看了几遍,感觉还是有点模糊,大概的意思算是有点理解了(如果有偏差,还请指出) 

18.Happens-Before Memory Model 
在定义JMM之前,先定义一个能满足JMM的所有要求,但还存在因果循环问题(causal loops,即因为A得出B,因为B得出C,因为C得出D,因为D得出A,因为这整条链没有一个起因,而实际上又会发生,这就叫因果循环问题)的一个模型。 
对这个模型的描述在这里就不翻译了,很简单,也很模糊,翻译肯定翻译不清楚的。 
这里就列举一下该模型存在的问题的例子:(这些问题都是由对模型的定义推出的合法的而且是正确的问题,但可以看到存在明显的因果循环,我们是不能接受的) 
 
在Happens-Before Memory Model中,会出现这样的结果x=y=1 

 
在Happens-Before Memory Model中,JVM允许这样的优化,所以也会导致因果循环的问题 

很明显,我们应该允许写操作的提前提交,以得到效能优化的效果(如上图的右图),但有些操作应该不允许的。不正式地说:当涉及到数据竞争的时候,就不应该允许提前提交,否则,就是允许的。 

七.Formal Specification of the Java Memory Model 
这里就只总结一下非常概括性的内容吧(因为这些定义都没有例子,难以理解,先看了下文再回来详细理解这里的定义) 

19.JMM的正式的定义 
定义动作和执行: 
用一个元组定义动作a的概念 
 
用另一个元组定义一个执行的概念 
 
然后对一些动作进行了定义: 
外部动作(external actions):略 
线程分歧动作(thread divergence action):略 
synchronizes-with:略 
happens-before略 
足够同步边缘(sufficient synchronization edges)略 
偏序和功能的限制(Restrictions of partial orders and functions)略 
设计良好的执行(Well-Formed Executions): 
  • 每个对变量x的读操作看到一个对x变量的写操作(注意,不是说前一个),所有对volatile变量的读和写都是volatile操作(没理解好~)
  • 同步顺序(Synchronization order)与程序顺序(program order)和互斥(mutual exclusion)保持一致
  • 执行遵守线程内(intra-thread语义,即在线程内与程序顺序一致)一致性
  • 执行遵守同步顺序(synchronization-order,这个也没理解好)一致性
  • 执行遵守同步顺序(happens-before)一致性

对执行的因果关系的需求(即解决上文所说的因果循环问题的需求): 
这是一个推导过程,需要满足9个条件,就可以推出这个结论,全是数学公式过程,在这里就不抄了(下文的例子貌似就是用这些条件作为依据推理的,看了下文后,再看这里应该能清晰点) 
能被观察到的举动:略 
永不终止的执行:略 

八.Illustrative Test Cases and Behaviors 

20.以下图片中出现的情况都是合法的 
至于为什么合法,有一定的解释:因为他可以满足上文中的“对执行的因果关系的需求”中的9个条件 
 
很明显,将y=1提前了,是允许的(文章中有根据条件对提交步骤的推理,证明是可行的,之后的例子都没有,如果时间空闲,后面将试着补充推导过程) 

 
也是一个指令的重排序,但注意:r1==1或者r2==2是不允许的:如果写提前了,他们对本地的读就会看不到这个写。 

 
编译器会优化这个==操作,变成if(true),所以将b=2提前了,也是允许的 

 
编译器会发现分支无论怎么走,都会执行a=1,所以也提前了 

 
编译器会发现r1要么为1要么为0,所以r2必然=1,所以也提前了 

 
这个现象感觉实在没办法解释了,文中的解释感觉也不通~~ 
翻译如下: 
编译器会发现,唯一有可能分配到x的值是0或者42。根据这个,编译器可以推断:当执行到r1=x,要么刚好执行了一个写x=42,要么刚好读x,并且看到了值42(为什么?根本解释不通的~)。无论是哪种情况,一个对x的读看到了值42,然后它会将r1=x改为r1=42,这也会允许y=r1转变为y=42,并且提早发生了,这样就出现了描述的情况:r1=r2=r3=42 

21.以下图片中的情况是不合法的 
 
 
但解释挺牵强的,说是由于安全的原因,比如在第二个图中,如果42是对某个对象的引用,而这个引用是Thread4持有的,他打算只有当z=1时,才让Thread1和Thread2看到。 
如果发生了tu2的情况,安全就没有保证了,所以不允许~~ 

九.Final Field Semantics 

22.final filed的需求和目标
  • final field的值将不会改变
  • 只包含final filed的对象在“成功构造”(注意,这是关键的必要条件,也就是在构造成功之后,this才能被别的线程看到)结束后,在可能有竞争的线程间传递,需要被认为是不变的。
  • 对final field的读,将最小化编译器/结构的消耗(cost)(相对于非final field)
  • final field的语义需要允许某些场景(如反序列化),在这些场景中,对象中的final field允许构造结束后被修改(顺便浏览了一下反序列化的源码(见以下代码),还真是先创建了对象,然后才读入field的值的,以后要是面试面到了final field,又可以忽悠一下)
  • Java代码  收藏代码
    1. Object curObj = curContext.getObj();  
    2. ObjectStreamClass curDesc = curContext.getDesc();  
    3. bin.setBlockDataMode(false);  
    4. defaultReadFields(curObj, curDesc);  


    23.final field safe context 
    为了防止final field在构造器中的设值与发生在之后的读的重排序(reorder),定义了一个叫final field safe context的区域。 
    文章是这样解释的:如果一个对象在final field safe context中被构造(注意,这是必要条件,如果是在final field safe context中,但对象没有被构造,也就没有该限制),对该对象的final field的值的读,将不会与发生在final field safe context中的写进行重排序。 
    举个例子:像在clone方法和ObjectInputStream.readObject方法中使用这样一个final field safe context就不会出现上述重排序的问题了(这些例子都是构造成功后修改final field的值的情况)。(但这个final field safe context怎么加?如果我们要人为地利用这个东西,怎么去编码呢?文章没有提及,而浏览了一下clone和readObject方法,也没有调用相应的本地方法,那这个final field safe context是如何被调用的呢?方法覆盖?) 
    在后文中有一个再详细点的解释,大概意思是可以这样做,用一个动作包含了final field safe context的范围,然后在动作结尾放置一个标志。 
    至于如何去实现,同样的,书中也只给出了一些定义,也就是说,只要满足这些定义的任何方法,都是可接受的,就可以得到一个满足我们需求的final field,想想也是,JMM就是一个定义,而不是一个实现。推理部分就不贴了,个人觉得,理解起来费劲,而且还没啥用~

    24.final field的正式语义 
    因为比较重要,这也贴一下原文,以防被小弟误译了 
    A freeze action on a final field f of an object o takes place when a constructor for o in which f is written exits,either abruptly or normally 
    如果一个对象o的包含对final field的写操作的构造方法发生,一个对final field的一个固化操作将会被调用,即使这个构造方法可以是突然的,或者正常的。 
    进一步的解释: 
    一些特殊的机制像反射,反序列化,是允许在构造结束后修改final field的值的。像可以使用java.lang.reflect中的setX(...)方法来达到这个效果。当这个fiedl是final的,这个方法只有满足2个条件才能设置成功,否则会抛IllegalAccessException异常:这个field不是静态的,并且对这个field设置setAccessible(true)成功。 

    25.Static Final Fields 
    如果final field是静态的,JMM不需要提供任何额外的措施来保证,因为这个final field的值是由class(注意,不是实例instance)的初始化来设置的,而class的初始化与静态变量的读是由虚拟机来保证其同步的。

    分享到:
    评论

    相关推荐

      JSR133中文版,java并发进阶必读

      JSR133中文版,java并发进阶必读。

      JSR-133-Java Memory Model Specification 中文版+英文版+参考资料

      JSR133-memory_model-1_0-pfd2-spec....读JSR133笔记 - 十年磨一剑 - ITeye博客.pdf The JSR-133 Cookbook.pdf jsr-133-faq.pdf JSR-133-Appr-The Java Memory Model.pdf 深入理解Java内存模型之系列篇 - CSDN博客.pdf

      JSR133中文版.pdf

      本文是JSR-133规范,即JavaTM内存模型与线程规范,由JSR-133专家组开发。本规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。本规范的标准内容将合并到JavaTM语言规范、JavaTM虚拟机规范...

      java并发编程实战中文加英文版加源码

      Java 5以及6在开发并发程序中取得了显著的进步,提高了Java虚拟机的性能以及并发类的可伸缩性,并加入了丰富的新并发构建块。在《JAVA并发编程实践》中,这 些便利工具的创造者不仅解释了它们究竟如何工作、如何使用...

      JSR133规范中文版

      JSR133规范

      JSR133中文版

      JSR133中文版,java内存模型,JSR标准,理解java内存模型

      JSR-133 Java 内存模型 英文版

      This document is the proposed final draft version of the JSR-133 specification, the Java Memory Model (JMM) and Thread Specification. This specification is intended to be part of the JSR-176 umbrella ...

      JSR-133-ZH.pdf

      本文是JSR-133规范,即JavaTM内存模型与线程规范,由JSR-133专家组开发。本规范是JSR-176(定义了JavaTM平台 Tiger(5.0)发布版的主要特性)的一部分。本规范的标准内容将合并到JavaTM语言规范、JavaTM虚拟机规范...

      JAVA并发编程实践.pdf

      英文版:Java Concurrency in Practice 内容简介 《JAVA并发编程实践》随着多核处理器的普及,使用并发成为构建高性能应用程序的关键。Java 5以及6在开发并发程序中取得了显著的进步,提高了Java虚拟机的性能以及...

      JSR133中文版1-复制.pdf

      JSR133中文版 .

      jsr133_content.pdf

      JSR-133内存模型手册 带目录 This document is the JSR-133 specification, the JavaTM Memory Model and Thread Specification (JMM), as developed by the JSR-133 expert group. This specification is part of ...

      jsr311-api-1.1.1-API文档-中文版.zip

      标签:javax、jsr311、api、中文文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请放心...

      Java并发编程实战

      本书作者都是Java Community Process JSR 166专家组(并发工具)的主要成员,并在其他很多JCP专家组里任职。Brian Goetz有20多年的软件咨询行业经验,并著有至少75篇关于Java开发的文章。 《Java并发编程实战》深入浅...

      jsr311-api-1.1.1-API文档-中英对照版.zip

      标签:javax、jsr311、api、中英对照文档、jar包、java; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请...

      Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

       《Java JDK 7学习笔记》将IDE操作纳为教学内容之一,使读者能与实践结合,提供的视频教学能更清楚地帮助读者掌握操作步骤。 内容简介 书籍 计算机书籍  《java jdk 7学习笔记》是作者多年来教学实践经验的总结...

      jsr305-3.0.2-API文档-中文版.zip

      标签:findbugs、jsr305、google、jar包、java、中文文档; 使用方法:解压翻译后的API文档,用浏览器打开“index.html”文件,即可纵览文档内容。 人性化翻译,文档中的代码和结构保持不变,注释和说明精准翻译,请...

      JSR133(JavaTM内存模型与线程规范).zip

      JSR-133 :Java TM 内存模型与线程规范

    Global site tag (gtag.js) - Google Analytics