
课程咨询: 400-996-5531 / 投诉建议: 400-111-8989
认真做教育 专心促就业
Volatile关键字是大多数Java程序员都需要熟练掌握的一个编程知识,而本文我们就通过案例分析来简单了解一下,Java函数式编程Volatile关键字的用法分享。
1、Volatile关键字
volatile的意义与定义
volatile和synchronized这两个关键字在并发编程中都扮演着极为重要的角色,这里我会先讨论volatile。
volatile是轻量级的synchronized,它在开发中保证了共享变量的可见性和与其相关指令的有序性。
可见性就是指当某个共享变量被一个线程修改时,其他线程能够立即获知这种修改。
有序性指的是对于针对被volatile修饰的变量的单个操作,其之前与之后的指令在经过编译器的指令重排序后不会越过我们做出的该操作
volatile定义:当某个字段被声明为volatile时,Java线程模型确保所有线程看到的这个变量的值皆统一。
这里需要先了解一下相关CPU术语:
内存屏障:本质是一组处理器指令,用于实现对内存操作的顺序限制
缓冲行:CPU高速缓存中可以分配的小存储单位
原子操作:不可拆分的一个或一系列操作
缓冲行填充:当系统识别到从内存中读取的信息是可以缓存的,处理器即读取整个高速缓冲行至适当的缓存层级中(L1,L2,L3等等)
缓存命中:若缓冲行填充操作的内存位置仍然是下次处理器访问的地址,则处理器会从高速缓存中取操作数,而不去读取内存
写命中:当处理器将操作数写回一个缓存区域时,先检查这个缓存的内存地址是否还存在于缓存行中,若存在一个有效的缓存行,则将这个操作数写回缓存行而非内存
写缺失:一个有效的缓存行被写入不存在的缓存内存区域
底层原理
一句话概括其原理的本质就是利用了CPU的缓存一致性。
详细来说,针对有volatile关键字的共享变量进行写操作时,其指令会多出一行汇编代码,该代码为lock前缀的一行指令。这样的指令在多核处理器下会引发两件事:
将当前处理器缓存行的数据写回到系统内存
这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效
什么概念呢?我们知道CPU的速度非常快,而读写内存相对慢许多,所以CPU会将内存中数据缓存到Cache中再操作。而volatile的lock指令会造成这样的影响:当触发lock指令时,直接将该变量所对应的缓存行数据写回系统内存。
那么根据CPU的缓存一致性特性,当一个处理器的缓存回写时,所有从内存中缓存了对应该区域的缓存内容的缓存行都会被标记为过期数据,当这些处理器想要对该行数据操作时,就需要先重新从内存中读取一次数据,再进行操作。
总结下来,volatile通过两件事保证了可见性:
lock前缀指令会引起处理器缓存回写到内存
该指令有两种实现:1、总线锁定;2、缓存锁定
锁总线即是声言LOCK#信号,该信号确保在声言期间,一切其他处理器对于总线的占用都会被阻塞,从而确保该处理器独享了内存,但是这么做开销很大,毕竟这就相当于将一个多处理器CPU退化到了单处理器
另一种做法就是我们的lock,这种做法不声言LOCK#,而是锁定这块缓存并且将其写回内存,利用缓存一致性机制,其他处理器此时会获知这块内存对应的缓存已经过期无效,进而组织同时修改由两个以上处理器缓存的内存区域,从而确保了修改的原子性。
一个处理器的缓存回写到内存会导致其他处理器的缓存无效
那么有序性又是怎么实现的呢?主要是依靠在针对volatile的指令前后添加内存屏障来实现的,当编译器进行指令重排序时,该屏障会阻止指令越过该屏障,这样,位于该指令之前的指令重排序后仍然只能处于该指令之前,其后的指令同理。
volatile功能总结
前面已经说了,volatile关键字主要就是两个功能:
可见性,确保任何针对该变量的操作皆可见
当对非volatile变量进行读写的时候,每个线程先从主内存拷贝变量到CPU缓存中,如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPUcache中。
而标注了volatile的变量不会被缓存在寄存器或者对其他处理器不可见的地方,保证了每次读写变量都从主内存中读,跳过CPUcache这一步。当一个线程修改了这个变量的值,新值对于其他线程是立即得知的。这就是内存可见性。
有序性,确保针对该变量的写操作不会因为重排序而语义错误
指令重排序的目的是优化指令、提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。指令重排序包括编译器重排序和运行时重排序。
比如a=1;b=2;complete=true,由于这三个指令互不相关,所以进行指令重排后,很可能会变成complete=true;a=1;b=2那么此时如果另外一个一直挂起的线程打算通过complete看看a,b是否赋值完成,就会出现问题。
而针对volatile修饰的变量,在读写操作指令前后会插入内存屏障,指令重排序时越过屏障重排序,从而避免了某些这种错误
【免责声明】本文系本网编辑部分转载,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责。如涉及作品内容、版权和其它问题,请在30日内与管理员联系,我们会予以更改或删除相关文章,以保证您的权益!更多内容请加danei456学习了解。欢迎关注“达内在线”参与分销,赚更多好礼。