Java并发:Java内存模型(二)

 

接上一篇讲到了Java内存模型的基础 ,本篇接着来谈谈重排序 的问题

2 .重排序

    重排序是指编译器和理器化程序性能而指令序列行重新排序的一种手段。

   2.1 数据依赖性

      如果两个操作访问同一个量,且两个操作中有一个写操作,此时这两个操作之间就存在数据依赖性。数据依下列3型,如表3-4所示。

       上面3种情况,只要重排序两个操作的序,程序的果就会被改。前面提到过编译器和理器可能会操作做重排序。编译器和理器在重排序,会遵守数据依赖性,编译器和理器不会改存在数据依关系的两个操作的序。这里所的数据依仅针对单理器中行的指令序列和线程中行的操作,不同处理器之和不同线程之的数据依性不被编译器和理器考

2.2 as-if-serial语义

        as-if-serial语义的意思是:不管怎么重排序(编译器和理器了提高并行度),(单线程)程序的执果不能被改编译器、runtime理器都必遵守as-if-serial语义。为了遵守as-if-serial语义编译器和理器不会存在数据依关系的操作做重排序,因为这种重排序会改变执果。但是,如果操作之不存在数据依关系,些操作就可能被编译器和理器重排序。了具体明,看下面的代示例。

  上面3个操作的数据依关系如3-6所示。

                                    

3-6所示,AC存在数据依关系,同BC也存在数据依关系。因此在 最终执行的指令序列中,C不能被重排序到AB的前面(C排到AB的前面,程序的果将会被改变)。但AB没有数据依关系,编译器和理器可以重排序AB序。

3-7程序的两种序。

as-if-serial语义单线程程序保了起来,遵守as-if-serial语义编译器、runtime理器共同为编单线程程序的程序员创建了一个幻单线程程序是按程序的序来行的。as-if-serial语义使单线程程序无需担心重排序会干,也无需担心内存可问题

 

2.3 程序顺序规则

       根据happens-before的程序规则,上面的面的示例代存在3happens-before关系。

1A happens-before B

2B happens-before C

3A happens-before C

       这里的第3happens-before关系,是根据happens-before传递性推出来的。 这里A happens-before B,但实际执B却可以排在A之前行(看上面的重排序后的执行顺序)。如果A happens-before BJMM并不要求A一定要在B之前行。JMM仅仅要求前一个操作(执行的果)后一个操作可,且前一个操作按序排在第二个操作之前。里操作A的执果不需要操作B;而且重排序操作A和操作B后的果,与操作A和操作B按happens-before行的果一致。在种情况下,JMM认为这种重排序并不非法(not illegal),JMM许这种重排序。

       在计算机中,件技和硬件技有一个共同的目:在不改程序果的前提下,尽可能提高并行度。编译器和理器遵从一目,从happens-before的定可以看出,JMM同遵从一目

 

2.4 重排序对多线程的影响

       来看看,重排序是否会改线程程序的果。看下面的示例代

 class ReorderExample {
        int a = 0;
        boolean flag = false;
        public void writer() {
            a = 1; // 1
            flag = true; // 2
        }
        public void reader() {
            if (flag) { // 3
                int i = a * a; // 4
            }
        }
    }
}

      flag变量是个标记,用来标识变a是否已被写入。里假有两个线ABA首先行writer()方法,随后B线程接着reader()方法。线B行操作4,能否看到线A在操作1对共享a的写入呢?

      答案是:不一定能看到。

       由于操作1和操作2没有数据依关系,编译器和理器可以对这两个操作重排序;同,操作3和操作4没有数据依关系,编译器和理器也可以对这两个操作重排序。先来看看,当操作1和操作2重排序,可能会生什么效果?看下面的程序,如3-8所示。

                                         

 

        如3-8所示,操作1和操作2做了重排序。程序线A首先写标记变flag,随后线程B读这量。由于条件判断真,线Ba。此a没有被线A写入,在这里多线程程序的语义被重排序破坏了!

       下面再让看看,当操作3和操作4重排序生什么效果(借助个重排序,可以顺 便说明控制依性)。下面是操作3和操作4重排序后,程序行的,如3-9所示。

                                

        在程序中,操作3和操作4存在控制依关系。当代中存在控制依,会影响指令序 列执行的并行度。此,编译器和理器会采用猜Speculation行来克服控制相关性并 行度的影响。以处理器的猜测执例,线B理器可以提前取并a*a,然后把计算临时保存到一个名重排序冲(Reorder BufferROB)的硬件存中。当操作3的条 件判断为,就把该计果写入i中。从图3-9中我可以看出,猜测执实质操作34做了重排序。重排序在里破坏了多线程程序的语义

        在单线程程序中,存在控制依的操作重排序,不会改变执果(也是as-if-serial语义允许对存在控制依的操作做重排序的原因);但在多线程程序中,存在控制依的操作重排序,可能会改变程序的果。

 

3. 顺序一致性

       顺序一致性内存模型是一个理参考模型,在设计候,理器的内存模型和语言的内存模型都会以顺序一致性内存模型作参照。

3.1 数据竞争与顺序一致性

       当程序未正确同步,就可能会存在数据争。Java内存模型数据争的定如下。

在一个线程中写一个变量,

在另一个线程读同一个变量,

而且写和读没有通过同步来排序。

当代中包含数据,程序的行往往反直果(前一章的示例正是如此)。如果一个多线程程序能正确同步,个程序将是一个没有数据争的程序。JMM对正确同步的多线程程序的内存一致性做了如下保。 如果程序是正确同步的,程序的执行将具有序一致性(Sequentially Consistent——即程序的执果与程序在序一致性内存模型中的果相同上我就会看到,这对于程序员是一个极的保里的同步是指广上的同步,包括常用同步原语 (synchronizedvolatilefinal)的正确使用。

3.2 顺序一致性内存模型

        顺序一致性内存模型是一个被算机科学家理想化了的理参考模型,它程序提供了极强的内存可性保序一致性内存模型有两大特性。

1)一个线程中的所有操作必按照程序的序来行。

2)(不管程序是否同步)所有线程都只能看到一个一的操作序。在序一致性内存模型中,每个操作都必须原子行且立刻所有线程可

序一致性内存模型程序提供的视图3-10所示。

                                       

        在概念上,序一致性模型有一个一的全局内存,个内存通一个左右摆动的开关可以连接到任意一个线程,同每一个线程必按照程序的序来行内存/写操作。从上面的示意图可以看出,在任意时间点最多只能有一个线程可以接到内存。当多个线程并发执行中的开关装置能把所有线程的所有内存/写操作串行化(即在序一致性模型中,所有操作之间具有全序关系)。为了更好行理解,下面通两个示意对顺序一致性模型的特性做一步的明。

       假有两个线AB发执行。其中A线程有3个操作,它在程序中的序是: A1→A2→A3B线程也有3个操作,它在程序中的序是:B1→B2→B3

       假设这两个线程使用监视来正确同步:A线程的3个操作行后监视,随后B线程取同一个监视。那么程序在序一致性模型中的行效果将如3-11所示。

             

在我再假设这两个线程没有做同步,下面是个未同步程序在序一致性模型中的行示意,如3-12所示。

             

     未同步程序在序一致性模型中然整体序是无序的,但所有线程都只能看到一个一致的整体执序。以上图为例,线AB看到的序都是:

B1→A1→A2→B2→A3→B3。之所以能得到个保是因为顺序一致性内存模型中的每个操作必须立即任意线程可

       但是,在JMM中就没有个保。未同步程序在JMM中不但整体的序是无序的,而且所有线程看到的操作序也可能不一致。比如,在当前线程把写的数据存在本地内存中,在没有刷新到主内存之前,这个写操作仅对当前线程可;从其他线程的角度来观察,会认为这个写操作根本没有被当前线行。只有当前线程把本地内存中写的数据刷新到主内存之后,这个写操作才能其他线程可。在种情况下,当前线程和其他线程看到的操作执序将不一致。

3.3 同步程序的顺序一致性效果

看下面的示例代 

          

        在上面示例代中,假A线writer()方法后,B线reader()方法。是一个正 确同步的多线程程序。根据JMM范,程序的果将与程序在序一致性模型中的执行果相同。下面是程序在两个内存模型中的,如3-13所示。

                 

       顺序一致性模型中,所有操作完全按程序的顺序串行行。而在JMM中,界区内的代码可以重排序(但JMM不允许临界区内的代逸出界区之外,那会破坏监视器的语义)。JMM会在退出界区和界区两个关键时间点做一些特别处理,使得线程在两个时间点具有与序一致性模型相同的内存视图(具体细节后文会明)。线A界区内做了重排序,但由于监视器互斥行的特性,里的线B根本无法线A临界区内的重排序。这种重排序既提高了行效率,又没有改程序的果。从这里我可以看到,JMM在具体实现上的基本方针为:在不改(正确同步的)程序执行结果的前提下,尽可能地为编译器和理器的化打开方便之

3.4 未同步程序的执行特性

        对于未同步或未正确同步的多线程程序,JMM只提供最小安全性:线时读取到的 值,要么是之前某个线程写入的,要么是默认值0NullFalse),JMM证线操作取到的值不会无中生有(Out Of Thin Air)的冒出来。实现最小安全性,JVM在堆上分配象时,首先会内存空间进行清零,然后才会在上面分配象(JVM内部会同步两个操作)。因此,在已清零的内存空间Pre-zeroed Memory)分配,域的默初始化已完成了。

        JMM不保未同步程序的果与程序在序一致性模型中的果一致。因为如果想要保证执果一致,JMM需要禁止大量的理器和编译器的化,这对程序的行性能会产生很大的影响。而且未同步程序在序一致性模型中,整体是无序的,其行结果往往无法知。而且,保未同步程序在两个模型中的果一致没什么意

        未同步程序在JMM中的,整体上是无序的,其果无法知。未同步程序在两个模型中的执行特性有如下几个差异。

1)序一致性模型保证单线程内的操作会按程序的行,而JMM不保证单线程内的操作会按程序的顺行(比如上面正确同步的多线程程序在界区内的重排序)。一点前面已经讲过了,里就不再述。

2序一致性模型保所有线程只能看到一致的操作序,而JMM不保所有线程能看到一致的操作执序。一点前面也已经讲过里就不再述。

3JMM不保证对64位的long型和double量的写操作具有原子性,而序一致性模型保证对所有的内存/写操作都具有原子性。

3个差异与理器总线的工作机制密切相关。在算机中,数据通过总线理器和内存之间传递。每次理器和内存之的数据传递都是通一系列步来完成的,一系列步称之为总线Bus Transaction)。总线包括Read Transaction)和写事Write Transaction)。从内存送数据到理器,写事理器送数据到内存,每个事会 读/写内存中一个或多个物理上连续的字。里的关是,总线会同步试图使用总线的事务。在一个理器总线总线会禁止其他的理器和I/O设备执行内存的/写。下面,让一个示意总线的工作机制,如3-14所示。

       由图可知,假设处理器ABC总线发总线这时总线仲裁(Bus Arbitration)会对竞争做出裁决,里假设总线在仲裁后判定理器A争中获胜总线仲裁会确保所有 处理器都能公平的访问内存)。此时处理器A继续它的总线,而其他两个理器要等待处理器A总线完成后才能再次行内存访问。假理器A总线(不管这个总线务还是写事),理器D总线发起了总线,此时处理器D求会被总线禁止。总线的些工作机制可以把所有理器内存的访问以串行化的方式来行。在任意时间点,最多只能有一个理器可以访问内存。个特性确保了总线之中的内存/写操作具有原子性。

        在一些32位的理器上,如果要求64位数据的写操作具有原子性,会有比大的开。为了照顾这理器,Java范鼓励但不JVM64位的long量和double量的写操作具有原子性。当JVM理器上运行,可能会把一个64long/double量的写操作拆分为两个32位的写操作来行。两个32位的写操作可能会被分配到不同的总线务中执行,此时对这64量的写操作将不具有原子性。

    本文内容来自《java并发编程的艺术》一书。未完.......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值