volatile:

定义:Java编程语言允许线程访问共享变量,为了确保共享变量内被准确和一致性地更新,线程应该确保通过排它锁单独获得这个变量。根据volatile的定义,volatile有锁的语义。

作用:1.保证共享变量的可见性(这是volatile作为轻量级锁的基础);

    这里可见性的意思是:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值(与上篇定义的可见性有点区别啊,这里与上篇相比没有强调因重排序带来的有序性问题,进而导致的操作间可见性问题,也就是后面操作本来应该能看到前面操作结果的,结果因为重排序而看不到了,导致出现不可见性问题,笔者认为这本质上还是属于重排序问题。所以上篇定义的可见性,最后一句存在错误,但为了强调可见性与重排序(有序性)之间的区别,笔者并没有修改,当然也保留了这部分的解释)。

   2.禁止重排序(根据happens-before规则)

实现原理:

 1.保证共享变量可见性的实现原理(以X86处理器来分析):

  instance = new Single();//instance是volatile变量

  转换成汇编代码,如下:

  0x01a3deld: movb $0x0,0x1104800(%esi);0x01aa3de24:lock addl $0x0,(%esp);

  有volatile修饰的共享变量进行写操作的时候会多出第二行(带lock前缀的)汇编代码。

  Lock前缀的指令在多核处理器下的作用:

  1.将当前处理器的缓冲行的数据写会到系统内存;

  Lock前缀指令导致在执行指令期间,声言处理器的LOCK#信号。在多处理环境中,LOCL#信号确保会在声言该信号期间,处理器可以独占任何共享内存。(采取机制有锁总线,锁缓存,目前的处理大多选择后者,因为锁总线的开销太大)。  

  对于目前的处理器,如果访问的区域已经缓存在处理器内部,则不会声言LOCK#信号,处理器会锁定这块内存的缓存并刷新到内存,并使用缓存一致性机制来确保修改的原子性,此操作称为“缓存锁定”,缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据。同时,Lock前缀指令会引起处理器缓存刷新到内存。

  2.这个写内存的操作会使在其它CPU里缓存了该内存地址的数据无效。

  实现机制:一个处理器的缓存刷新到内存会导致其它处理器的缓存无效。处理器使用嗅探技术来保证它的内部缓存,系统内存和其他处理器的缓存的数据在总线上保持一致。

  缓存一致性协议:在多处理下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址(中的数据)被修改,就会将当前处理器的缓存行设置为无效状态,当处理器对这个数据进行操作的时候,会重新从系统内存把数据读到处理器缓存里(看到这里是不是明白了只对volatile变量的写操作进行了处理(增加lock前缀),因为处理器缓存里的数据有么有效,有效就是最新值,要么无效,需要到主内存去读取)。

现在总结一下LOCK前缀指令的作用:

1.确保处理器修改共享数据的原子性(此时修改的值还在缓存),通过锁总线,锁缓存实现的

2.将修改后的值刷新到内存,同时刷新内存的这个动态,会被其他处理器嗅探到,然后其他处理器会将他们缓存里的数据置为无效状态。

2,volatile禁止重排序的实现原理

在讲解实现原理之前,我们先说一下volatile的内存语义:

内存语义的实现也需要可见性作为基础的。内存屏障只是禁止重排序的基础。

volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值(所有的共享变量)刷新到主内存。

volatile读的内存语义:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效(本地内存中的所有数据都无效了),线程接下来将从主内存中读取数据。

volatile内存语义的实现:

volatitle重排序规则表:

从上表可知:

1、当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序。

2、当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。

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

通过volatile重排序的规则,编译器编译时会在这三种情况下插入相应的内存屏障,来保证volatile的内存语义。插入内存屏障的规则:

1、在每volatile写,之前插入StoreStore内存屏障指令,之后插入StoreLoad屏障;

2、在每volatile读,之后插入内存LoadLoad,LoadStore屏障指令。

这些内存屏障插入措施是非常保守的。因为有些内存屏障可以根据内存访问的具体情况(比如只有一个volatile读时,所有的屏障都可以省略)是可以省略掉的。尤其是这些措施是独立于处理器平台之上的,不同的处理器重排序的规则都不一样。比如,X86处理器,只允许写/读重排序,这种情况下,上面的内存屏障插入规则只需要StoreLoad就可以了。

根据volatile禁止重排序的规则,以及内存屏障指令的作用,我们可以总结volatile的一个特性:

对volatile变量的单个读/写,可以看成是使用同一个锁对这些单个volatile读/写做了同步,正是如此,volatile被称为轻量级锁。在功能上,锁比volatile更强大;在可伸缩性和执行性能上,volatile更有优势。

不同的处理器进行重排序的规则也不相同,以X86处理器为例,它允许的重排序就只有写-读。

synchronized

final

Java并发之同步原语的更多相关文章

  1. java并发之同步辅助类CyclicBarrier和CountDownLatch

    CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier).它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门, ...

  2. Java并发之同步工具类

    1. CountDownlatch(计数器) 描述: 一个同步工具类,允许一个或多个线程等待其它线程完成操作 类图 通过指定的count值进行初始化,调用await方法的线程将被阻塞,直到count值 ...

  3. java并发之同步辅助类CountDownLatch

    CountDownLatch 含义: CountDownLatch可以理解为一个计数器在初始化时设置初始值,当一个线程需要等待某些操作先完成时,需要调用await()方法.这个方法让线程进入休眠状态直 ...

  4. java并发之同步辅助类(Semphore、CountDownLatch、CyclicBarrier、Phaser)

    线程同步辅助类,主要学习两点: 1.上述几种同步辅助类的作用以及常用的方法 2.适用场景,如果有适当的场景可以用到,那无疑是最好的 semaphore(seməˌfôr) 含义 信号量就是可以声明多把 ...

  5. JAVA并发之锁获取步骤及锁优化

    在另外的两篇文章中先后介绍了轻量级同步关键字volatile和重量级锁关键字synchronized,这两个关键字是Java语言中进行线程同步的基本方式(当然还有ReentrenLock等显式锁方式) ...

  6. 从同步原语看非阻塞同步以及Java中的应用

    非阻塞同步:基于冲突检测的乐观并发策略,通俗讲就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了,如果争用数据有冲突那就采用其他的补偿措施(最常见的就是不断重试直到成功),这种乐观的并发策略 ...

  7. java并发之线程同步(synchronized和锁机制)

    使用synchronized实现同步方法 使用非依赖属性实现同步 在同步块中使用条件(wait(),notify(),notifyAll()) 使用锁实现同步 使用读写锁实现同步数据访问 修改锁的公平 ...

  8. java并发之固定对象与实例

    java并发之固定对象与实例 Immutable Objects An object is considered immutable if its state cannot change after ...

  9. 深入理解Java并发之synchronized实现原理

    深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入 ...

随机推荐

  1. 蓝桥杯算法训练 区间k大数查询

    算法训练 区间k大数查询   问题描述 给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个. 输入格式 第一行包含一个数n,表示序列长度. 第二行包含n个正整数,表示给定的序列. 第三个 ...

  2. MR execution in YARN

    Overview YARN provides API not for application developers but for the great developers working on ne ...

  3. quartz入门实例

    一 Quarta介绍 1 Quartz是什么 Quartz就是一个纯 Java 实现的作业调度工具,相当于数据库中的 Job.Windows 的计划任务.Unix/Linux 下的 Cron,但 Qu ...

  4. codeforces 228E The Road to Berland is Paved With Good Intentions(2-SAT)

    Berland has n cities, some of them are connected by bidirectional roads. For each road we know wheth ...

  5. HDU 4169 Wealthy Family(树形DP)

    Problem Description While studying the history of royal families, you want to know how wealthy each ...

  6. WebExtensions & tabs.executeScript()

    tabs.executeScript() https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs ...

  7. dpr dproj 扩展名区别,dprdproj

    这段时间用xe6,看了下目录下生成的一些文件,因为隐藏了扩展名,看到两个名字一样的文件,右键属性看了下,同名但扩展名不同,百度了下区别,没有找到答案,问群里的朋友才知道区别,特此记录下来: dpr:D ...

  8. Keil MDK中Image~~RW_IRAM1~~ZI~~Limit(~表示$)

    ARM程序的组成 此处所说的“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的bin映像(image)文件,这一点清注意区别.            一个ARM程序包含3部分:RO, ...

  9. 【codevs1282】约瑟夫问题 Treap

    题目描述 有编号从1到N的N个小朋友在玩一种出圈的游戏.开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边.编号为1的小朋友站在编号为N的小朋友左边.首先编号为1的小朋友开始报数,接 ...

  10. android应用打前需要准备些啥?

    发布之前我们需要准备的东西 参考了google官方和结合国内开发经验 1) 用户协议(本地.网络.API) 2) 签名文件(用于为APP加密,唯一标签) 3) 程序图标(第一个面对用户,准备不同的但合 ...