volatile关键字
例子
public class VolatileTest {
private static final Logger LOGGER = MyLoggerFactory.getSimplestLogger();
private static volatile int MY_INT = 0;
public static void main(String[] args) {
new ChangeListener().start();
new ChangeMaker().start();
}
static class ChangeListener extends Thread {
@Override
public void run() {
int local_value = MY_INT;
while ( local_value < 5){
if( local_value!= MY_INT){
LOGGER.log(Level.INFO,"Got Change for MY_INT : {0}", MY_INT);
local_value= MY_INT;
}
}
}
}
static class ChangeMaker extends Thread{
@Override
public void run() {
int local_value = MY_INT;
while (MY_INT <5){
LOGGER.log(Level.INFO, "Incrementing MY_INT to {0}", local_value+1);
MY_INT = ++local_value;
try {
Thread.sleep(500);
} catch (InterruptedException e) { e.printStackTrace(); }
}
}
}
}
当有volatile关键字的时候,打印结果为:
Incrementing MY_INT to 1
Got Change for MY_INT : 1
Incrementing MY_INT to 2
Got Change for MY_INT : 2
Incrementing MY_INT to 3
Got Change for MY_INT : 3
Incrementing MY_INT to 4
Got Change for MY_INT : 4
Incrementing MY_INT to 5
Got Change for MY_INT : 5
当没有volatile关键字的时候,打印结果为:
Incrementing MY_INT to 1
Incrementing MY_INT to 2
Incrementing MY_INT to 3
Incrementing MY_INT to 4
Incrementing MY_INT to 5
ChangeListener类中的循环将进入死循环
解释
-
每一个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存,变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。
-
volatile关键字用于对 jvm 说"警告,此变量可能在其他线程中修改"。如果没有此关键字,JVM 可以自由地进行一些优化,例如从不刷新某些线程中的本地副本(也就无法保证从主内存加载到线程工作内存的值是最新的)。volatile会强制线程更新每个副本变量在堆中变量的值。
-
volatile声明了变量值的一致性;而static声明变量的唯一性。static不能保证工作区与主存区变量值的一致性;除非变量的值是不可变的,即再加上final的修饰符,否则static声明的变量,不是线程安全的。因此上面的例子如果将volatile去除之后,ChangeListener会陷入死循环是因为ChangeListener类中变量local_value因为jvm的优化一直未被更新。
文章参考: