1.一句代码就可以搞定volatile是什么含义:
volatile boolean done = false;
while(!done){
dosomething();
}
如上只有在状态控制线程自己对done修改,而其他线程只是读取时;才有想要的效果;
2.下面开始稍微解释,物理背景:
由于CPU 内存的速率的巨大差异,CPU直接操作的那些句柄不是直接从内存里面直接读写操作的,而是存在类似于cache 这样的部件来缓和速率差异。例如i++这样一个简单的操作是分为三步进行的:
- CPU 发送指令 从 L1 cache load i 到 寄存器;
- 寄存器的 i+1;
- 寄存器的i 写入到L1 cache;
3.volatile的含义在于(MESI协议):
- 当一个Core对缓存中的变量进行write时,其他的Core的缓存控制器会监听到这个写操作,会将自己的相应的缓存行设置为invalid;//这一块协议是物理电路实现的
- 当在其它Core在对自己的缓存行进行读写这个变量时,发现自己L1cache缓存中的该变量的缓存行是无效的,那么它就会从其他的核心的cache中或者主存读取数据然后赋值到缓存,然后再对缓存进行读写;
- 每次Core对变量的操作,都要从cache中读取;
4.下面举个例子来解释:
4.1如下代码:
public class Test {
static volatile int count = 0;
public static void main(String[] args) throws InterruptedException {
Runnable target = new Runnable() {
@Override
public void run() {
for (int i=0;i<100000;i++){
count++;
}
}
};
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
t1.start();
t2.start();
Thread.currentThread().sleep(1000);
System.out.println(count);//一般不是100000*2
}
}
4.2 程序执行的模拟:
- t1 加载count到自己的L1cache;
- t2 发现自己要加载的变量时可缓存的,那么会从其他的核心的L1cache把这一行数据同步过来;
- t1 在自己寄存器完成+1操作,counttmp1 = 1,同时t2也完成了+1操作counttmp2 = 1;
- t1 写counttmp1到L1cache此时t1的count=1,同时t2的缓存控制器监听CPU总线会监听到修改,会将自己的缓存行设置为invalid;
- t2在把counttmp2写到缓存时,发现自己缓存行无效,会同同步t1的cache过来;
- 然后把counttmp2 赋值给count,此时t2的count=1;
好了,问题就出在t2的直接写缓存上【count=1】;但是我们期望的是count=2;
这就可以解释被volatile修饰的i,在多线程进行++操作最终结果并不是我们想要的结果了;