保证有序性、原子性、可见性
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将使用自旋锁。
相关文章推荐
- volatile 可以保证可见性,但不能保证原子性。某种意义上是线程不安全的
- 精确解释java的volatile之可见性、原子性、有序性(通过汇编语言)
- Java之多线程内存可见性_2(volatile不能保证原子性)
- Java并发_volatile实现可见性但不保证原子性
- 并发编程的3个概念 ,原子性,可见性,有序性!
- Java内存模型(五):原子性、可见性与有序性
- Java多线程总结(5)— 原子性、可见性、有序性和并发库的原子性操作
- Java并发_volatile实现可见性但不保证原子性
- 聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性
- Java是怎么保证原子性,可见性
- 浅谈Java并发编程系列(四)—— 原子性、可见性与有序性
- volatile实现可见性但不保证原子性
- 为什么volatile不能保证原子性而Atomic可以(valatile只保证可见性,不保证原子性)
- volatile实现可见性但不保证原子性
- java 并发概念与内存分析,原子性、可见性、有序性
- volatile实现可见性但不保证原子性
- 线程安全的三大特性(原子性、可见性、有序性)
- volatile 可以保证可见性,但不能保证原子性
- 聊聊高并发(十九)理解并发编程的几种"性" -- 可见性,有序性,原子性
- Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例