volatile的应用

1. 引言

volatile是轻量级的synchronized,它在多处理器开发中保证了共享变量的“可见行”,可见性的意思是,当一个线程修改一个共享变量,另一个线程能读到这个修改的值。如果volatile变量修饰符使用恰当的话,它比synchronized的使用和执行成本更低,因为它不会引起线程上下文切换和调度。

2. CPU缓存行

2.1 CPU常用术语

术语 英文 描述
内存屏障 memory barriers 是一组处理器指令,用于实现对内存操作对顺序限制
缓存行 cache line CPU高速缓存中可以分配的最小存储单位,缓存行是2的整数幂个连续字节,最常见的是64个字节,高速缓存中加载和修改数据都是以缓存行为基本单位进行处理的
原子操作 atomic operations 不可中断的一个或一系列操作
缓存行填充 cache line fill 将主存中的数据缓存到缓存行中
缓存命中 cache hit 处理器从缓存中读取到数据,而不是从主存中读取
写命中 write hit 当处理器操作数写回到一个内存缓存区域时,它首先会检查这个缓存的内存地址是否在缓存行中,如果存在一个有效的缓存行,则处理器将这个操作数写回到缓存,而不是写回到内存,这个操作被称为写命中

2.2 详解CPU缓存行

如果要了解缓存,就必须要了解缓存的结构,以及多个CPU核心访问缓存存在的一些问题和注意事项。
每个缓存里面都是由缓存行组成的,缓存系统中是以缓存行(cache line)为单位存储的。缓存行是2的整数幂个连续字节,最常见的缓存行大小是64个字节。当多线程修改互相独立的变量时,如果这些变量共享同一个缓存行,就会无意中影响彼此的性能,这就是伪共享。
volatile的应用
需要注意,数据在缓存中不是以独立的项来存储的,cache是由缓存行组成的,通常是64字节(比较旧的处理器缓存行是32字节),并且它有效地引用主内存中的一块地址。一个Java的long类型是8字节,因此在一个缓存行中可以存8个long类型的变量。
volatile的应用

2.3 缓存行的带来的好处

如果你访问一个long数组,当数组中的一个值被加载到缓存中,它会额外加载另外7个。因此你能非常快地遍历这个数组。事实上,你可以非常快速的遍历在连续的内存块中分配的任意数据结构。
因此如果你数据结构中的项在内存中不是彼此相邻的(链表,我正在关注你呢),你将得不到免费缓存加载所带来的优势。并且在这些数据结构中的每一个项都可能会出现缓存未命中。

2.4 缓存行带来的问题

不过,所有这种免费加载有一个弊端。设想你有两个单独并且是相连的long类型变量,我们称它为head,另一个称它为tail。现在,当你加载head到缓存的时候,很可能你也免费加载了tail。这样就会带来一个问题,当core1修改了head变量时,core2需要读取tail变量时,由于缓存一致性协议,虽然core1对tail变量没有任何修改,缓存行和主存中对应对内容都更新了,core2对应的缓存行已经失效,需要重新读取。
请记住我们必须以整个缓存行作为单位来处理(这是CPU的实现所规定的),不能只把head标记为无效。如果两个线程同时分别写head和tail变量,那么对性能对影响会更糟。
volatile的应用
当然解决这个问题对方法,就是将变量对长度增加为缓存行对长度。

部分内容参考:https://www.jianshu.com/p/e338b550850f