您的位置:首页 > 其它

保证有序性、原子性、可见性

2017-02-25 13:16 253 查看

volatile

有序性



  

  上图表示,当第一个操作为XX操作,第二个操作为YY操作时,是否允许重排序,NO表示不允许,空表示允许。

  

  从表中可以看出

当第二个操作是volatile写时,不管第一个操作是什么,都不能
c185
重排序。这个规则确保volatile写之前的操作不会被编译器重排序到volatile写之后。

当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。这个规则确保volatile读之后的操作不会被编译器重排序到volatile读之前。

当第一个操作是volatile写,第二个操作是volatile读时,不能重排序。

原子性

  对任何单个volatile变量的读/写具有原子性,包括64位的long/double变量。

可见性

  对一个volatile变量进行读操作时,总是能看到(任意线程)对这个volatile变量最后的写入。

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,接着从主内存中读取共享变量

volatile实现原理

  前面讲述了源于volatile关键字的一些使用,下面我们来探讨一下volatile到底如何保证可见性和禁止指令重排序的。

  下面这段话摘自《深入理解Java虚拟机》:

  “观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令”

  lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能:

它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;

它会强制将对缓存的修改操作立即写入主存;

如果是写操作,它会导致其他CPU中对应的缓存行无效。

有序性

  JMM允许同步代码块里面的代码重排序,但是不允许重排序后的代码逸出到同步块之外,那样会破坏临界区的语意。

原子性

  锁是的最主要特性便是原子性,此处忽略解释。

可见性

  获得锁 heppens before 释放锁

当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到内存中

当线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须从主内存中读取该变量

final

有序性

在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

对于引用类型,写final域的重排序规则对编译器和处理器增加了如下约束:

在构造函数内对一个final引用的对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

为什么需要对final字段做这些有序性保证?

  在旧的Java内存模型中 ,最严重的一个缺陷就是线程可能看到final域的值会改变。比如,一个线程当前看到一个整形final域的值为0(还未初始化之前的默认值),过一段时间之后这个线程再去读这个final域的值时,却发现值变为了1(被某个线程初始化之后的值)。最常见的例子就是在旧的Java内存模型中,String的值可能会改变。

  为了修补这个漏洞,JSR-133专家组增强了final的语义。通过为final域增加写和读重排序规则,可以为java程序员提供初始化安全保证:只要对象是正确构造的(被构造对象的引用在构造函数中没有“逸出”),那么不需要使用同步(指lock和volatile的使用),就可以保证任意线程都能看到这个final域在构造函数中被初始化之后的值。

happens before

有序性

JSR-133使用happens-before的概念来指定两个操作之间的执行顺序。由于这两个操作可以在一个线程之内,也可以是在不同线程之间。因此,JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。规则如下:

程序顺序规则:在一个单独的线程中,按照程序代码的执行流顺序,(时间上)先执行的操作happen—before(时间上)后执行的操作。

管理锁定规则:一个unlock操作happen—before后面(时间上的先后顺序,下同)对同一个锁的lock操作。

volatile变量规则:对一个volatile变量的写操作happen—before后面对该变量的读操作。

线程启动规则:Thread对象的start()方法happen—before此线程的每一个动作。

线程终止规则:线程的所有操作都happen—before对此线程的终止检测,可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。

线程中断规则:对线程interrupt()方法的调用happen—before发生于被中断线程的代码检测到中断时事件的发生。

对象终结规则:一个对象的初始化完成(构造函数执行结束)happen—before它的finalize()方法的开始。

传递性:如果操作A happen—before操作B,操作B happen—before操作C,那么可以得出A happen—before操作C。

CAS

CAS全称为Compare And Swap,或是Compare And Set

原子性

CAS操作可以分为以下三个步骤:

读旧值(即从系统内存中读取所要使用的变量的值,例如:读取变量i的值)

求新值(即对从内存中读取的值进行操作,但是操作后不修改内存中变量的值,例如:i=i+1,这一步只进行i+1,没有赋值,不对内存中的i进行修改)

两个不可分割的原子操作,这两个操作是不可分割的原子操作,必须两个同时完成,比较内存中变量现在的值与 最开始读的旧值是否相同(即从内存中重新读取i的值,与一开始读取的i进行比较)

如果这两个值相同的话,则将求得的新值写入内存中(即:i=i+1,更改内存中的i的值)

如果这两个值不相同的话,则重复步骤1开始

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败。这种基于冲突检测的乐观并发策略,当线程数目非常多的情况下,失败的概率会指数型增加。

  在JDK1.5中引入了底层的支持,在Integer,Long等类型上都公开了 CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法。在运行CAS的平台上,运行时把它们编译为相应的机器指令,如果处理器不支持CAS指 令,那么JVM将使用自旋锁。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息