在多线程并发变成中,volatile是扮演者重要的角色,下面主要从使用、原理来分析:
使用方法:
public class volatileTest {
//将成员变量声明为volatile
volatile long vl = 0L;
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
原理:
定义:(引自Java语言规范第三版)Java编程语言允许多线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过拍他所单独获取这个变量。Java提供了volatile,在某些请鲁昂下比锁更加方便,一旦该成员变量被声明为volatile 所有读取这个变量的线程看到的这个值都是一致的。
白话版定义:Java支持多线程同时访问一个对象或者对象的成员变量,由于每个线程可以拥有这个变量的拷贝,所以一个线程在执行过程中看到变量不一定是最新的。关键字volatile可以用来修饰成员变量,就是告知程序任何对该变量的访问均需要从共享内存中获取,而对他的改变必须同步刷新到共享内存,他能保证所有线程对变量访问的可见性
确保内存可见性:在volatile修饰的共享变量会在写操作的时候的在其代码中加上lock前缀指令,用来完成这个任务--->将当前处理器缓存航的数据回写到系统内存(共享内存),从而使其他CPU里缓存了该内存地址的数据无效,在多处理器下,通过实现缓存一致性协议,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置为无效,当处理器对这个数据进行操作的时候,会重新从系统内存(共享内存)中把数据读取到处理器的缓存里。
volatile特性:
- 对一个volatile变量的读,总是能看到任意线程对这个volatile变量最后的写入
- 即使是64位的long或者double类型变量,只要他是volatile类型,对该变量的读写都具有原子性
- 如果是多个volatile操作,类似valotile++ 等复合操作,则不具有原子性
volatile读的内存语义:
当读一个volatile变量时,JMM(
Java内存模型),会把该线程对应的本地内存置为无效,线程接下来将从朱内存中(共享内存)中读取共享变量。
volatile写的内存语义:
当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到朱内存中(共享内存,之所以说主内存是相对线程自己的内存而言)。
valatile内存语义的实现:
基于保守策略的JMM内存屏障插入策略:
- 在每个volatile写操作的前面插入一个StoreStore屏障
- 在每个volatile写操作的后面插入一个StoreStore屏障
- 在每个volatile读操作的后面插入一个LoadLoad屏障
- 在每个volatile读操作的后面插入一个LoadStore屏障
注:内存屏障:是一组处理器指令,用于实现对内存操作的顺序限制
屏障类型 | 指令示例 | 说明 |
---|---|---|
LoadLoad Barriers | Load;LoadLoad;Load2 | 确保Load1数据的装载先于oad2及所有后续装载指令的装载 |
StoreStore Barriers | Store1;StoreStore;Store2 | 确保Store1数据对其他处理器可见(刷新到内存)先于Sotre2以及所有后续存储执行的存储 |
LoadStore Barriers | Load1;LoadStore;Store2 | 确保Load1数据装载先于Store2及所有后续的存储指令刷新到内存 |
StoreLoad Barriers | Store1;StoreLoad;Load2 | 确保Store1数据对其他处理器变得可见(指刷新到内存)先于Load2及所有后续装载指令的装载 StoreLoad Barriers 会使该屏障之前的所有内存访问指令完成之后,才执行该内存屏障之后的内存访问指令 |
内存屏障代码分析和释义:
package com.hhx.offline_tools.encode;
public class volatileTest {
int a;
volatile int v1 = 1;
volatile int v2 = 2;
void readAndWriteVolatile() {
/**
* 第一个volatile读
*
* 在第一个volatile读 和第二个volatile读之间 插入了LoadLoad屏障
* 用来禁止上面volatile读和下面的volatile读重排序
*
* 同时这里还省略了一个LoadStore屏障,因为下面的普通写根本不可能越过上卖弄的volatile读
*
*/
int i = v1;
/**
* 第二个volatile读
*
* 第二个volatile读和普通写之间
* 这里省略了LoadLoad屏障,因为下面根本没有普通读操作
* 这里插入了一个LoadStore内存屏障 用来禁止下面的普通写和上面的volatile读重排序
*/
int j = v2;
/**
*普通写
*
*普通写和第一个volatile写之间 插入了 一个StoreStore屏障 用来禁止上面的普通写和下面的volatile写重排序
*
*/
a = i + j;
/**
* 第一个volatile 写
*
* 在第一一个volatile 写和第二个volatile写 之间省略了StoreLoad 屏障,仅仅插入StoreStore屏障即可,因为下面跟着一个volatile
*
* 同时在这里还插入了一个StoreStore屏障 用来禁止上卖弄的volatile写与下面的volatile写 重排序
*
*/
v1 = i + 1;
/**
* 第二个volatile写
*
* 在第二个volatile写之后 加入了StoreLoad 屏障 用来防止上面的volatile写与后面可能有的volatile读写重排序
* 这一步不能省略,因为在第二个volatile写之后,方法立即return 此时编译器肯恩无法准确判定后面是否会有volatile读或者写
* 为了安全起见,编译器通常会在这里插入一个StoreLoad屏障
*/
v2 = j + 2;
}
}