Volatile关键字
1. volatile 是轻量级的synchronized,在多处理器开发中保证了共享变量的“可见性”,即当一个线程修改共享变量时,另外一个线程能读到这个修改的值,和Synchronized区别在于,它的使用的执行成本更低,它不会引起线程上下文的切换和调度。
2. 如果一个字段被声明为bolatile,Java线程内存模型确保所有线程看到这个变量的值是一致的
1. volatile 是轻量级的synchronized,在多处理器开发中保证了共享变量的“可见性”,即当一个线程修改共享变量时,另外一个线程能读到这个修改的值,和Synchronized区别在于,它的使用的执行成本更低,它不会引起线程上下文的切换和调度。
2. 如果一个字段被声明为bolatile,Java线程内存模型确保所有线程看到这个变量的值是一致的
在多核CPU中,每条线程可能运行于不同的CPU中,因此每个线程运行时有自己的高速缓存,
初始时,两个线程分别读取i的值存入各自所在的CPU的高速缓存当中,然后线程1进行加1操作,然后把i的最新值1写入到内存。此时线程2的高速缓存当中i的值还是0,进行加1操作之后,i的值为1,然后线程2把i的值写入内存。
最终结果i的值是1,而不是2。这就是著名的缓存一致性问题。通常称这种被多个线程访问的变量为共享变量。
下面我们来看一些CPU的相关术语:
一般而言,为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。如果对声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。所以,在多处理器下,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
总结一下volatile的两条实现原则:
1. Lock前缀指令会引起处理器缓存回写到内存
2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
1. Lock前缀指令会引起处理器缓存回写到内存
2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效
Volatile关键字的两层语义:
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2. 禁止进行指令重排序。
对于以下代码:
//线程1
boolean stop = false;
while(!stop){
doSomething();
}
//线程2
stop = true;
每个线程在运行过程中都有自己的工作内存,那么线程1在运行的时候,会将stop变量的值拷贝一份放在自己的工作内存当中。那么当线程2更改了stop变量的值之后,但是还没来得及写入主存当中,线程2转去做其他事情了,那么线程1由于不知道线程2对stop变量的更改,因此还会一直循环下去。
而使用了volatile关键字后:
1. 使用volatile关键字会强制将修改的值立即写入主存;
2. 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
3. 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
1. 使用volatile关键字会强制将修改的值立即写入主存;
2. 使用volatile关键字的话,当线程2进行修改时,会导致线程1的工作内存中缓存变量stop的缓存行无效(反映到硬件层的话,就是CPU的L1或者L2缓存中对应的缓存行无效)
3. 由于线程1的工作内存中缓存变量stop的缓存行无效,所以线程1再次读取变量stop的值时会去主存读取。
最后,需要注意的是,volatile保证了多线程操作时不同线程对共享变量的可见性,但不确保原子性,比如多个线程对一个变量进行共1000次累加,无法保证最终结果是1000。