一个小程序
public class TestThread {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (flag) {
}
System.out.println("t1 end");
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("flag set to false");
}
}
输出结果:
flag set to false
在上面的小程序中,创建一个线程t1,这个线程判断flag为true的时候进行死循环,当flag为false的时候结束循环。启动线程,1秒之后将flag设为false。但是期望的结果并没有出现,线程t1并没有退出循环。这说明主线程修改flag的值之后,线程t1并没有感知到flag的变化。
究其原因
如图,线程t1执行时,会将flag从主存中拷贝一个副本到其线程本地(CPU缓存)中,每一次while循环读取的都是本地的flag。同理,主线程执行时,也会从主存中拷贝一个flag副本到其线程本地,将flag设置为false时,修改的是本地flag的值,并没有修改主存中flag的值。在上面的小程序中,线程t1看不到flag值的修改,所以线程t1永远不会结束。
使用volatile修饰变量实现可见性
public class TestThread {
private static volatile boolean flag = true;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while (flag) {
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1 end");
});
t1.start();
Thread.sleep(1000);
flag = false;
System.out.println("flag set to false");
}
}
输出:
flag set to false
t1 end
在上面的代码中,只是将flag用volatile修饰,就得到了我们预期的结果,主线程将flag设为false之后,线程t1感知到flag的修改,退出循环,结束线程。原因在于,使用volatile修饰的变量,每一次读取都需要从主存中读取,对该变量的修改,每一次修改完成之后都要将新值刷新到主存中,由此保证了变量在线程之间的可见性。
volatile修饰引用类型
当使用volatile修饰引用类型的时候,只能保证引用本身的可见性,不能保证内部字段的可见性。比如:
public class TestThread {
private static class A {
boolean flag = true;
void m() {
while (flag) {}
System.out.println("hahah end");
}
}
private static volatile A a = new A();
public static void main(String[] args) throws InterruptedException {
new Thread(a::m).start();
Thread.sleep(1000);
a.flag = false;
System.out.println("a.flag set to false");
}
}
其他触发缓存同步刷新的情况
除了volatile之外,还存在一些语句会触发缓存同步刷新。如:System.out.println()和Thread.sleep()。println()方法加了synchronized,锁机制保证了每次执行都会把共享内存中的数据同步到工作内存中。而Thread.sleep()方法是因为,CPU在空闲的时候会主动同步刷新缓存中的数据。
public class TestThread {
private static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (flag) {
System.out.println();
}
System.out.println("end");
}).start();
Thread.sleep(1000);
flag = false;
System.out.println("flag set to false");
}
}