1.背景
计算机具有一定量的主存,用来存储我们程序相关联的数据。当你声明一个变量(例如 flag在我们下面的类中),计算机会留出一个特定的内存位置来保持那个变量的值。大多数CPU能够直接操作主存中的数据。其他CPU只能读取和写入主存位置。这些计算机必须从主存读取数据到寄存器中,在寄存器上操作,然后将数据存储到主存中。然而,即使CPU可以直接在主存中操作数据,通常也有一组寄存器可以保存数据,而寄存器中数据的操作通常比在主存中操作数据快得多。因此,当计算机执行你的代码时,寄存器的使用是非常普遍的。
从逻辑的角度来看,每个线程都有自己的寄存器集。当操作系统将某个线程分配给CPU时,它将CPU寄存器加载到特定于该线程的信息中;在分配给CPU的不同线程之前,它保存寄存器信息。因此,线程从不共享保存在寄存器中的数据。让我们看看这适用于一个java程序。当我们想要终止一个线程时,我们通常使用一个已完成的标志。线程(或Runnable对象)包含代码,如:
2.例子
public void run( ) {
while (!done) {
foo( );
}
}
public void setDone( ) {
done = true;
}
声明 done变量,将一个特定的内存位置(例如,0xff12345)赋给变量 done 然后设置内存的的值为0(等价于false)
private boolean done=false;
线程1执行 run()方法 ,并将run()编译成一组指令
Begin method run //开始运行run方法
Load register r1 with memory location 0Xff12345 //将内存 0Xff12345值赋给寄存器r1
Label L1: //标签 l1锚点
Test if register r1 == 1 //将r1==1比较,此时r1为0
If true branch to L2 //如果为真就跳转到标签l2,即跳出循环
Call method foo //调用方法foo
Branch to L1 //跳转到标签l1
Label L2: //标签 l2锚点
End method run //结束循环
同时,线程2调用setDone(),查看指令:
Begin method setDone //开始执行setDone方法
Store 1 into memory location 0xff12345 //把1值赋给内存0xff12345
End method setDone //结束setDone
你可以看到这个现象:run()方法中r1未从内存(0xff12345)中获取改变的值1。因此,run()从未终止方法
现在我们重新声明done如下:
private volatile boolean done = false;
重新查看下run()方法的指令集:
Begin method run
Label L1:
Test if memory location 0xff12345 == 1 //注意这里已经直接读取主存中的(0xff12345)内容了!
If true branch to L2
Call method foo
Branch to L1
Label L2:
End method
因此这一次我 们在调用setdone()方法,即可退出循环。
注:java虚拟机可能使用寄存器来处理volatile变量,只要符合volatile可见性的语义!这是一个必须遵守的原则,而不是实际执行的原则。
另外我们可以使用synchronized 关键字来代替volatile,原理是在进入同步边界,会发出信号使寄存器失效,强制从内存重新加载,再退出同步边界时,又会强制从它的局部变量写入内存。
3.volatile误用问题
简单分析,可能由于指令重排问题,非原子性问题造成,恩,下回分析!