多线程4(volatile)
1.原因
2.方案
3.JMM角度出发
1.为什么用volatile
volatile的意思是“易变的”
用来解决内存可见性问题引发的线程安全问题
编译器优化:写编译器的大佬们为了均衡小菜鸡们的实力,在编译器中加入了优化机制
public class Demo19 {
public static volatile int flag = 0;
public static void main(String[] args) {
Thread t1 = new Thread(()->{
/*try {
Thread.sleep(5000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
while (flag == 0){
/*try {
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}*/
//加上sleep就不会被优化了
}
System.out.println("t1线程结束");
});
Thread t2 = new Thread(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入flag的值");
flag = scanner.nextInt();
System.out.println("t2结束");
});
t1.start();
t2.start();
}
}
这个代码,创建两个线程,t1线程一直循环,t2线程来控制t1线程的while判断条件,正常情况下两个线程开始执行,当t2执行将flag修改后,t1会感知到flag被修改从而结束循环,但是实际情况却是t1貌似无法感知到t2的操作,无法结束循环
产生这种情况的原因就在于编译器的优化机制,CPU的调度极快,当两个线程启动后,while已经疯狂执行多次了,while判断从CPU的指令角度来看分为load和cmp操作,load的开销相对cmp很大,短短时间内循环了上万次,编译器发现load每次读到的都是相同值同时并没有做出修改,此时编译器就做出了大胆的操作,把load优化掉,之后的循环只从寄存器、缓存中读取flag,此时t2对flag进行修改t1无法感知到,t1没有从内存读取,而是从缓存中读取
2.解决方法
volatile解决内存可见性问题
用volatile来修饰某个变量,告诉编译器这个变量是“易变的”,后续对其进行读写,不需要进行优化
注意:volatile只针对一个线程读一个线程写,并没有产生互斥,或者原子性
3.JMM角度看待内存可见性
Java内存模型
Java进程每一个线程都会有一份工作内存,这些线程共享一个主内存
当线程针对某个数据进行操作
修改:把数据从主内存拷贝到工作内存,对工作内存进行修改,写回主内存
读取:把数据从主内存拷贝到工作内存,从工作内存读取
t1读取的是工作内存,哪怕t2修改变量后,t2把值写会主内存,但是t1的工作内存副本不会自动更新,使用t1一直读取的是旧值,t2修改的是主内存,二者没有联系
t1工作内存就相当于主内存的一个副本,直接修改主内存,不会直接影响到t1的工作内存
更多推荐


所有评论(0)