可见性
可见性是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改。
我们知道共享变量是存储在主内存中的,每个线程使用的时候从主内存复制到自己的工作内存,所以线程在操作这个变量的时候是在自己的工作内存中,操作完毕才会将值刷新到主内存,也就是说其他线程刷新了主内存的值,而我们当前线程是无法感知的,会继续操作自己工作空间的值,进而最终导致主内存的共享变量不是我们预期的结果。
public class VolatileTest {
/**
* 执行出现死循环,加上volatile后则正常
*/
static boolean stop = false;
// volatile boolean stop = false;
public static void main(String[] args) throws Exception{
VolatileTest v = new VolatileTest();
Thread ta = new Thread(()->v.execute());
ta.start();
Thread ta1 = new Thread(()->v.execute2());
ta1.start();
Thread ta2 = new Thread(()->v.execute3());
ta2.start();
Thread.sleep(2000);
Thread tb = new Thread(()->v.shutdown());
tb.start();
Thread.sleep(500);
System.out.println("stop="+String.valueOf(stop));
}
public void execute(){
while(!stop){
String a = "a";
// stop = false;
// System.out.println("123"); // 1)当有synchronized()同步机制的时候,会保证可见性。
// 2)jvm会尽可能的在空闲的时候去同步主存的共享变量,所以当线程存在Thread.sleep时候,会尽可能同步数据
}
System.out.println("execute:stop");
}
public void shutdown(){
stop = true;
System.out.println("do stop");
}
public void execute2(){
System.out.println("execute2 start");
while(!stop){
String a = "a";
try {
Thread.sleep(500);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
System.out.println("execute2:stop");
}
public void execute3(){
System.out.println("execute3 start");
while(!stop){
String a = "a";
System.out.println("123");
}
System.out.println("execute3:stop");
}
}
执行完结果:
do stop
…(程序一直在运行,进入无线循环)
我们看到do stop这个线程已经将stop修改为false,但是线程还没没终止,这就印证了我们的说法。
过程:
- main线程,将将主存中flag的值复制到自己的工作内存中。
- 接着启动线程execute,此时该线程也会将主存中stop的值复制到该线程的工作内存中。
- 接着while循环从自己工作内存中读取stop的值,一直为true,一直循环。
- 2s后启动线程shutdown将stop的值从自己的工作内存中修改为false。
- 虽然线程shutdown工作内存flag的值被修改了,但是什么时候刷新到主内存是不确定的。
- 即使立即刷新到主内存,但是其他线程也是无法感知的。
- 所以while循环一直读取的自己工作内存的flag,就处于无限循环中。
- 线程execute2和线程execute3感知到了,说明synchronized和Thread.sleep(ms, ns)可以保证可见性。
- 将stop 添加volatile后,execute1也可以感知到stop 值的变化。
结论: