Java并发编程之volatile的应用
Posted 木易森林
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程之volatile的应用相关的知识,希望对你有一定的参考价值。
在多线程的并发编程中synchronized和volatile都扮演着重要的角色。volatile是轻量级的synchronized,它在多处理器的开发中保证了共享变量的可见性,可见性的意思是当一个线程修改一个共享变量时,另一个线程能够读取到这个修改值。如果volatile变量使用恰当的话,他会比synchronized的使用和执行成本更低。因为他不会引起线程上下文的调度和切换。本节将详细接收volatile的实现原理。
一.volatile的定义与实现原理
Java语言规范第三版对volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量可以被准确和一致的更新,线程应该确保使用排它锁单独获得这个变量。Java提供的volatile,在某些情况下比锁要更加的方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
在了解volatile实现原理之前,我们先来看下与其实现原理相关的CPU术语与说名。见下表:
术语 | 英文单词 | 术语描述 |
内存屏障 | memory barriers | 是一组处理器指令,用于实现对内存操作的顺序限制 |
缓冲行 | cache line | 缓存中可以分配的最小存储单元,处理器处理缓存线时,会加载整个缓冲线,需要使用多个主内存读周期 |
原子操作 | atomic operations | 不可中断的一个或则一系列操作 |
缓冲行填充 | cache line fill | 当处理器识别到从内存中读取的操作数是可缓冲的,处理器读取整个缓冲行到合适的缓存 |
缓存命中 | Cache hit | 如果进行高速缓存行填充操作的内存位置仍是下次处理器访问的地址时,处理器从缓存中读取操作数而不是从内存读 |
写命中 | write hit | 当处理器将操作数写回到一个内存缓存的区域时,它首先会检查这个缓存的内存地址是否在缓存行,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回内存,这个操作被称为写命中。 |
写缺失 | write miss the Cache | 一个有效的缓存行被写到一个无效的内存区域 |
volatile是如何来保证可见性的呢?让我们在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,CPU会做什么事情。
Java代码如下:
instance = new Singleton(); // instance是volatile变量
转化为汇编代码后,如下:
0x01a3de1d: movb $0×0,0×1104800(%esi);
0x01a3de24: lock addl $0×0,(%esp);
有volatile变量修饰的共享变量进行写操作的时候会多出第二行汇编代码,通过查IA-32架构软件开发者手册可知,Lock前缀的指令在多核处理器下会引发了两件事情:
1.将当前处理器缓存行的数据写会到系统内存
2.这个写会内存的操作会使在其他CPU里缓存了该内存地址的数据失效
为了提高处理速度,处理器不直接和内存进行通信,而是将系统内存数据先读到内部缓存(L1,L2或则其他)后进行操作,但操作玩不知道何时会写会内存,如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条LOCK前缀的指令。将这个变量所在缓存行的数据写回到内存。但是就是写回到内存,加入其他CPU的的缓存还是旧的,再执行操作同样会有问题。所以在多处理下,为了确保各个处理器缓存的内容一致,就会实现缓存一致性协议,就是每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
volatile的两条实现原则
1.Lock前缀指令会引起处理器缓存回写到内存
2.一个处理器的缓存回写到内存会导致其他处理器的缓存无效
以上是关于Java并发编程之volatile的应用的主要内容,如果未能解决你的问题,请参考以下文章