volatile关键字是什么?( 二 )


@Override
public void run() {
try {
Thread.sleep(2000L);
System.out.println(\"flag 状态改变\");
flag = true;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
VolatileTest testVolatile = new VolatileTest();
Thread thread1 = new Thread(testVolatile.new ThreadOne());
Thread thread2 = new Thread(testVolatile.new ThreadTwo());
thread1.start();
thread2.start();
}
}
上述结果有可能在线程 2 执行完 flag = true 之后 。并不能保证线程 1 中的 while 能立即停止循环 。原因在于 flag 状态首先是在线程 2 的私有内存中改变的 。刷新到主存的时机不固定 。而且线程 1 读取 flag 的值也是在自己的私有内存中 。而线程 1 的私有内存中 flag 仍未 false 。这样就有可能导致线程仍然会继续 while 循环 。运行结果如下:
执行操作
执行操作
执行操作
flag 状态改变
任务停止
避免上述不可预知问题的发生就是用 volatile 关键字修饰 flag 。volatile 修饰的共享变量可以保证修改的值会在操作后立即更新到主存里面 。当有其他线程需要操作该变量时 。不是从私有内存中读取 。而是强制从主存中读取新值 。即一个线程修改了某个变量的值 。这新值对其他线程来说是立即可见的 。
指令重排序
一般来说 。处理器为了提高程序运行效率 。可能会对输入代码进行优化 。它不保证程序中各个语句的执行先后顺序同代码中的顺序一致 。但是它会保证程序最终执行结果和代码顺序执行的结果是一致的 。
比如下面的代码
int i = 0;
boolean flag = false;
i = 1;// 1
flag = true;// 2
代码定义了一个 int 型变量 。定义了一个 boolean 类型变量 。然后分别对两个变量进行赋值操作 。从代码顺序上看 。语句 1 是在语句 2 前面的 。那么 JVM 在真正执行这段代码的时候会保证语句 1 一定会在语句 2 前面执行吗?不一定 。为什么呢?这里可能会发生指令重排序(InstructionReorder) 。
语句 1 和语句 2 谁先执行对最终的程序结果并没有影响 。那么就有可能在执行过程中 。语句 2 先执行而语句 1 后执行 。
但是要注意 。虽然处理器会对指令进行重排序 。但是它会保证程序最终结果会和代码顺序执行结果相同 。那么它靠什么保证的呢?再看下面一个例子:
int a = 10;// 1
int r = 2;// 2
a = a + 3;// 3
r = a * a;// 4
这段代码执行的顺序可能是 1->2->3->4 或者是 2->1->3->4 。但是 3 和 4 的执行顺序是不会变的 。因为处理器在进行重排序时是会考虑指令之间的数据依赖性 。如果一个指令 Instruction2 必须用到 Instruction1 的结果 。那么处理器会保证 Instruction1 会在 Instruction2 之前执行 。
虽然重排序不会影响单个线程内程序执行的结果 。但是多线程呢?下面看一个例子:
// 线程1
String config = initConfig();// 1
boolean inited = true;// 2
// 线程2
while(!inited){
sleep();
}
doSomeThingWithConfig(config);
上面代码中 。由于语句 1 和语句 2 没有数据依赖性 。因此可能会被重排序 。假如发生了重排序 。在线程 1 执行过程中先执行语句 2 。而此时线程 2 会以为初始化工作已经完成 。那么就会跳出 while 循环 。去执行 doSomeThingWithConfig(config) 方法 。而此时 config 并没有被初始化 。就会导致程序出错 。
从上面可以看出 。指令重排序不会影响单个线程的执行 。但是会影响到线程并发执行的正确性 。
那么 volatile 关键字修饰的变量禁止重排序的含义是:
当程序执行到 volatile 变量的读操作或者写操作时 。在其前面的操作肯定已经全部进行 。且对后面的操作可见 。在其后面的操作肯定还没有进行
在进行指令优化时 。不能将 volatile 变量之前的语句放在对 volatile 变量的读写操作之后 。也不能把 volatile 变量后面的语句放到其前面执行
举个栗子:
x=0;// 1
y=1;// 2
volatile z = 2;// 3
x=4;// 4
y=5;// 5
变量z为 volatile 变量 。那么进行指令重排序时 。不会将语句 3 放到语句 1、语句 2 之前 。也不会将语句 3 放到语句 4、语句 5 后面 。但是语句 1 和语句 2、语句 4 和语句 5 之间的顺序是不作任何保证的 。并且 volatile 关键字能保证 。执行到语句 3 时 。语句 1 和语句 2 必定是执行完毕了的 。且语句 1 和语句 2 的执行结果是对语句 3、语句 4、语句 5是可见的 。
回到之前的例子:
// 线程1

推荐阅读