在多线程环境下,对于主内存中的共享变量,每个线程都有自己的一份拷贝值,便于自己线程内的操作,这也会导致一个问题,当有一个线程修改主内存中的变量值时,其他线程内拷贝的变量值还是旧值,没有获取最新的值。因此我们就可以使用volatile关键字修饰变量,它可以保证内存可见性和避免指令重排序。当有线程修改主内存中的变量值时,会导致其他线程中拷贝的变量值失效,当线程内读取自己线程内存中的变量值时会发现变量值已失效,就会从主内存重新读取变量值到自己线程内存中。以此来达到及时获取最新的变量值。但是volatile没有synchronized的同步原语,无法保证原子性。即在变量操作环境下仍然是不安全的。比如,volatile修饰的变量,两个线程a、b已经读取了主内存中的变量值并进行++操作,其中a线程先进行++操作,并更新主内存中的变量值,同时使b线程内的变量缓存值失效,但是如果b线程已经在a线程更新变量值之前就读取过了变量值,b就不会再重新读取主内存的值,即使这时缓存的值已经失效,b线程就会在原来的值基础上进行++操作,导致操作错误。
下面看一下在没有volatile关键字修饰时,修改变量的值不能时循环停止的情况。
/**
* Volatile保证变量内存可见性
* @author SN
*
*/
public class VolatileRead extends Thread{
private /*volatile*/ boolean flag=true;
public void setValue(boolean flag){
this.flag=flag;
System.out.println("设置flag:"+flag);
}
@Override
public void run(){
//run方法内是另启动的线程,其使用的flag值是线程内拷贝的主内存的值
while (flag) {
//System.out.println("#########");
}
System.out.println("循环结束,flag:"+flag);
}
public static void main(String[] args) throws InterruptedException {
VolatileRead vr=new VolatileRead();
vr.start();
Thread.currentThread().sleep(3000);
vr.setValue(false);
System.out.println("flag:"+vr.flag);
}
}
执行程序,可以看到程序没有停止,仍然在循环,这是因为虽然在main方法中修改了flag的值,但是由于while循环是在另外启动的线程中运行的,这个线程中读取的flag值是线程内缓存的主内存中的flag值,仍然是true,所以循环没有结束。如果将flag用volatile修饰就可以了。
另外在while循环中加上一句System.out的打印语句也可以使线程停止,因为System.out是jdk线程调用的,怀疑是自动刷新了线程内的flag值。