参考原文: http://www.importnew.com/18126.html
基本是对原文的精简, 推荐阅读原文以获得详细讲解.
volatile能够保证值在修改时会立即更新到主存. 当其他线程读取时读到的一定是最新的
volatile关键字和java内存模型有关, 所以先了解下内存模型
简述:
cpu运行速度远快于对内存的读写, 为了加快速度, 增加了高速缓存. 而一旦增加缓存, 就会出现缓存一致性问题.
解决思路:
1. 在总线上加Lock;
2. 通过缓存一致性协议;
早期使用思路1解决问题, 但是这样的话其他cpu无法访问内存, 效率低下.
所以出现了缓存一致性协议, 最出名的就是Intel的MESI协议.
核心思路:
当cpu写数据的时候, 如果发现操作的变量是共享变量, 即在其他cpu中也存在该变量的副本, 则发出信号通知其他cpu将该变量的缓存行置为无效状态. 让他们重新从内存中读取最新值.
并发编程常见问题:
1.原子性问题;
2. 可见性问题; 一个线程修改了变量, 其他线程能够立马看到修改的值.
3. 有序性问题;
有序性
虚拟机--指令重排序
//好例子
//线程1:
context = loadContext(); //语句1
inited = true; //语句2
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
语句1和语句2可能被重排, 导致线程2读取inited为true. 结果加载配置的时候却没加载到....
指令重排序不会影响单线程的执行, 但是会影响线程并发执行的正确性.
java内存模型
java内存模型规定所有的变量都存在主存当中, 每个线程有自己的工作内存, 线程对变量的操作都必须在工作内存中进行, 而不能直接对主存进行操作, 并且每个线程不能访问其他线程的工作内存.
x = 10; //语句1 **原子性**
y = x; //语句2 **不是原子性** 包含两个动作: 1.读取x的值; 2.写入工作内存;(注意工作内存四个字)
x++; //语句3 **不是原子性** 三步: 读-改-写
x = x + 1; //语句4 **不是原子性** 三步: 读-改-写
在32位平台上, 保存64位数据会分成两节. 导致不能保证原子性(现在据说已经实现原子性了?)...但是不论如何, 加上volatile关键字是良好的编程规范.
synchronized和lock自然也能保证可见性. 只是性能上没有volatile更优. 请因地制宜, 合理使用.
volatile的两层含义
- 保证了不同线程对这个变量操作时的可见性;
- 禁止进行指令重排序;
"禁止指令重排序"的实际使用
//线程1:
context = loadContext(); //语句1
volatile inited = true; //语句2 加上volatile,保证语句2在语句1之后执行.
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
volatile保证可见性, 却无法保证原子性.
使用volatile关键字的两个条件:
- 对变量的写操作不依赖于当前值;
- 该变量没有包含在具有其他变量的不变式中;
下面列举几个Java中使用volatile的几个场景。
1.状态标记量
volatile boolean flag = false;
while(!flag){
doSomething();
}
public void setFlag() {
flag = true;
}
volatile boolean inited = false;
//线程1:
context = loadContext();
inited = true;
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);
2.double check
class Singleton{
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if(instance==null) {
synchronized (Singleton.class) {
if(instance==null)
instance = new Singleton();
}
}
return instance;
}
}
网友评论