我先说说我的理解,如有不对请指正,volatile 有两个作用:
<1>防止编译器优化语句,例如:
int i = 0;
int i = 1;
int i = 2;
可能会被编译器优化成 int i = 2;
<2>保证 volatile 的可见性,这个我不是特别理解。看了一下一般使用 volatile 的场景有几种:
<2.1>使用 volatile 修饰一个线程上的变量,这种情况没有意义。
class VolatileTask2 implements Runnable
{
/*
* 在这种情况下,使用 volatile 没有什么意义,因为对于每一个 VolatileTask2 的实例都有自己的 stop 的拷贝。
* 每一个 stop 之间是相互独立的。
*/
volatile boolean stop = false;
public void run()
{
while(!stop){}
System.out.println(Thread.currentThread() + " stopped!");
}
}
public class VolatileTest2 {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++)
exec.execute(new VolatileTask2());
exec.shutdown();
}
}
<2.2> 使用 volatile 修饰一个对象上的变量,有许多的线程可能会操作该变量,这种情况也没有什么意义。
class VolatileHolder1
{
volatile int i = 0;
}
class VolatileTask1 implements Runnable
{
VolatileHolder1 vh;
public VolatileTask1(VolatileHolder1 vh){this.vh = vh;}
public void run()
{
/*
* ***这种情况下,volatile 无法保证线程安全,它只是保证了每次读取 i 的时候都是从主内存而不是线程栈上读取。***
* 在线程运行时,线程已经将变量复制到线程栈中,为什么输出的值还是有可能正确呢(不正确的结果是由于 vh.i++ 不是原子操作引起)。
* 因为,我们在复制的时候,只是将 VolatileTask1 的一个实例复制到了线程栈,
* 而这个实例持有一个 VolatileHolder1 vh 的引用,vh 引用的实际位置仍然是指向堆中内存。
*/
vh.i++;
}
}
public class VolatileTest1 {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
VolatileHolder1 vh = new VolatileHolder1();
for(int i = 0; i < 10; i++)
exec.execute(new VolatileTask1(vh));
exec.shutdown();
}
}
//这种情况下使用 volatile 没有任何的意义,因为它不能保证线程安全。
//1 4 3 2 1 5 6 7 8 9
<2.3>使用 volatile 修饰一个线程上的一个变量,但是有可能会有另外一个线程来更改这个值。这种情况下使用 volatile
才有意义。
package com.ch18.io;
/**
* 当我们不使用 volatile 关键字的时候:
* 在线程进入 run() 方法的时候,我们将它复制到线程栈中,与前面的不同的是,前面复制到线程栈的是一个指向堆内存的引用。
* 在将 stop 复制到线程栈之后,我们操作 stop 就是在线程栈上进行了。
* 而我们在 main() 方法中,修改的并不是 run() 方法线程栈上的 stop 的值,而在 run() 方法中读取的仍然是线程栈上的 stop。
*
* 当我们使用 volatile 关键字的时候,run() 方法和 stopMe() 方法都是读取主内存中的 stop 的值,所以可以正常运行。
*/
class StopTester implements Runnable {
private volatile boolean stop = false;
// private boolean stop = false;
public void stopMe() {
stop = true;
}
@Override
public void run() {
System.out.println("Thread start.");
while(!stop) {}
System.out.println("Thread stopped.");
}
}
public class TestVolatile {
public static void main(String[] args) throws Exception {
StopTester tester = new StopTester();
Thread thread = new Thread(tester);
thread.start();
Thread.sleep(1000);
System.out.println("Try stop...");
tester.stopMe();
Thread.sleep(1000);
}
}
也就是说,使用 volatile 的情况是这样的:在线程 A 上有一个变量 V,这个变量 V 在线程开始的时候被复制到线程栈中。而我们在线程 B 中有可能修改这个变量(不同于 <2.2>,在 <2.2> 中被复制到线程栈上的只是一个指向堆内存的指针,而我们并没有修改这个指针。我们修改的是这个指针指向的对内存中的值。),为了保证我们修改后的变量 V 能够被线程 A 看到(不加 volatile 的话,可能线程 B 修改的是 B 的线程栈中的变量 V,而线程 A 读取的是 A 的线程栈中的变量 V),就需要加上 volatile,强制A,B两个线程都在主内存上读取、写入变量 V。
volatile对指令重排的保证,才确保了它的可见性。题主列举的2.1,2.2两个场景并不是volatile适用的场景。
关于volatile的理解,可以看看http://ifeve.com/syn-jmm-volatile/