volatile的定义和实现原理
在Java语言规范中将volatile定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该通过排他锁单独获得这个变量。Java语言提供了volatile,在某型情况下比使用锁更加方便。如果一个字段被声明为volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
当我们对有volatile修饰的变量进行写操作的时候,会在下一行汇编加一个Lock指令。
Lock前缀的指令在多核处理器下会引起两件事情;
- 将当前处理器缓存行写回到系统内存
- 这个写回的操作会使其它CPU里缓存了该内存地址的数据无效。
volatile的内存语义
锁的happens-before规则保证释放锁和获取锁的两个线程之间的内存可见性,这意味着对一个volatile变量的读,总是能看到对这个volatile变量最后的写入。
**锁的语义决定了临界区代码的执行具有原子性。**这意味着,即使使64位的long型和double型变量,只要它使volatile变量,最该变量的读/写具有原子性。
volatile变量自身具有以下特性:
- 可见性。对一个volatile变量的读,总是能看到对这个volatile变量最后的写入。
- 原子性:对任意单个volatile变量的读/写具有原子性,当类似于volatile++这种复合操作不具有原子性。
volatile写的内存语义如下:
- 当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。
volatile读的内存语义如下:
- 当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。
volatile内存语义的实现
为了实现volatile内存语义,JMM会分别限制这两种类型的重排序。为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
- 在每个volatile写操作之前插入一个StoreStore屏障
- 在每个volatile写操作的后面插入一个StoreLoad屏障
- 在每个volatile读操作的后面插入一个LoadLoad屏障
- 在每个volatile读操作的后面插入一个LoadStore屏障。
volatile的使用优化
1 | /** 队列中的头部节点 */ |
在上面这段代码中,通过追加字节来优化系统的性能。这是因为在目前多少的CPU中,缓存行的宽度为64字节,如果队列头节点和尾节点都不足64字节的话,那么处理器就会将头接点和尾节点读入同一个缓存行中,当一个处理器试图修改头节点的时候,将会把整个缓存行锁定,在缓存一致性的机制的作用下,会导致其它处理器不能访问自己高速缓存中的尾节点。