`
gaoyuntao2005
  • 浏览: 311123 次
  • 性别: 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的初始化与静态变量的读是由虚拟机来保证其同步的。

    分享到:
    评论

    相关推荐

      Java JDK 6学习笔记——ppt简体版

      Java JDK 6学习笔记是为Java初学者量身定制的一份宝贵资料,它涵盖了Java编程的基础概念、语法以及核心特性。这份PPT简体版旨在帮助读者快速掌握Java开发的基本技能,逐步成为一名合格的Java程序员。 Java JDK...

      Java JDK 6学习笔记.zip

      这个压缩包“Java JDK 6学习笔记.zip”显然是一个包含有关Java JDK 6深入学习资源的集合,可能是PDF文档、笔记或者其他形式的教学材料。 在Java JDK 6中,有几个关键的特性值得我们关注: 1. **改进的性能**:JDK ...

      《Java JDK 6 学习笔记》配书示例

      通过《Java JDK 6 学习笔记》中的示例,读者可以深入了解这些特性,并通过实践来提升自己的Java编程技能。示例代码通常会涵盖这些新特性的用法,帮助读者更好地理解它们在实际开发中的应用。因此,这本书对于初学者...

      Java JDK 6学习笔记_pdf版(附课本代码)

      这份“Java JDK 6学习笔记”涵盖了从基础到高级的各种主题,是Java初学者和进阶者的重要参考资料。以下是笔记中可能包含的一些关键知识点: 1. **安装与配置**:介绍如何在不同操作系统(如Windows、Linux和Mac OS...

      良葛格java学习笔记

      【标题】:“良葛格java学习笔记”是一个关于Java编程语言的学习资料集合,由作者林信良编写,旨在分享他的学习心得和实践经验。这个资源特别关注JDK 6.0的新特性和功能,帮助读者更好地理解和掌握这一版本的Java...

      Java JDK 6学习笔记

      Java JDK 6学习笔记是针对Java开发环境的重要参考资料,它涵盖了Java Development Kit(JDK)6版本的关键特性和使用方法。JDK是Java编程语言的核心工具集,包含了编译器、调试器、JVM(Java虚拟机)以及一系列用于...

      Java+JDK+6学习笔记

      本学习笔记将深入探讨Java JDK 6中的关键知识点,帮助初学者和有经验的开发者更好地理解和应用这个版本的Java。 一、Java基础 Java是一种面向对象的编程语言,其特点包括跨平台性、垃圾回收机制和自动内存管理。在...

      Java JDK 8学习笔记.pdf

      不过,基于文档标题和描述,我们可以提炼出关于Java JDK 8学习笔记的一些知识点。 Java JDK 8是Java开发工具包的第8个版本,包含了Java编程语言的实现。JDK 8引入了众多新特性,对于Java开发者来说,是学习和升级的...

      Java JDK6学习笔记[ppt]

      Java JDK6学习笔记是针对Java开发初学者及进阶者的重要参考资料,主要涵盖了JDK6版本中的关键特性和编程实践。这份资料源自台湾JavaWorld论坛,由经验丰富的开发者分享,旨在帮助读者深入理解和掌握Java编程语言的...

      良葛格java jdk 6.0学习笔记代码.rar

      在"良葛格"的学习笔记中,可能包含了各种Java 6.0特性的实例代码,如使用新的JDBC API进行数据库操作,使用ScriptEngine执行脚本,或者利用NIO.2进行文件操作。通过这些示例,学习者可以更好地理解和应用这些新特性...

      Java秒杀系统方案优化高性能高并发学习实战源代码以及笔记..zip

      Java秒杀系统方案优化高性能高并发学习实战源代码以及笔记..zip 章节笔记 第1章-课程介绍及项目框架搭建 知识点 使用spring boot 搭建项目基础框架 使用Thymeleaf做页面展示,封装Result统一结果 集成 mybatis + ...

      Java JDK 6学习笔记——ppt简体版.rar

      1. **动态语言支持**:JDK 6引入了JSR 223(Scripting for the Java Platform),使得在Java中可以方便地使用脚本语言,如JavaScript、Groovy等。 2. **改进的Swing UI**:Swing组件得到了增强,例如JTable和JTree的...

      java学习笔记JDK6课件和课本代码

      Java是世界上最流行的编程语言之一,尤其在企业级应用开发领域占据主导地位。JDK(Java Development Kit)是Java开发的核心工具集,包含了编译器、调试器、文档生成工具等,使得开发者能够编写、测试和运行Java程序...

      林信良的jdk6学习笔记源代码

      《林信良的JDK6学习笔记源代码》是一份珍贵的学习资源,它包含了林信良在其著作《JDK6学习笔记》中所使用的全部源代码。这份源代码集可以帮助读者深入理解书中讲解的Java编程概念和技术,尤其对于正在学习JDK6版本的...

      学习EJB3基础知识笔记

      在"学习EJB3基础知识笔记"中,我们将深入探讨EJB3的主要特性和使用方法。 1. **注解驱动的编程模型**: EJB3引入了注解,极大地减少了XML配置文件的使用。通过在实体类、接口或实现类上添加注解,如`@Entity`、`@...

      一个轻量级,高性能的缓存构架,以android缓存而设计为初衷,也可以应用于一般的Java项目中。.zip

      3. **并发控制**:在多线程环境中,如何保证缓存数据的一致性和安全性,可能会涉及Java的synchronized关键字、volatile变量、ReentrantLock等并发工具的使用。 4. **缓存失效策略**:探讨不同的缓存失效策略,如...

      Spring5-尚硅谷框架课堂笔记.rar

      Spring框架是Java开发中的核心组件,它为应用程序提供了一个全面的基础设施,包括依赖注入(DI)、面向切面编程(AOP)以及数据访问等。Spring5是该框架的一个重要版本,引入了许多新特性以增强其功能和性能。下面将...

    Global site tag (gtag.js) - Google Analytics