一、内存可见性
1.1 Java内存模型(JMM)
1)什么是Java内存模型(JMM)? |
Java内存模型即Java Memory Model,简称JMM。用于屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各平台下都能够达到一致的内存访问效果,即实现“跨平台”。 |
2)JMM中的“主内存”概念和“工作内存”概念 | |
“主内存” | 硬件中的内存,在JMM中表述为“主内存”,其中存储了线程间的共享变量等数据。 |
“工作内存” | CPU寄存器和缓存等临时存储区,在JMM中表述为“工作内存”,每个线程都有自己的“工作内存”。 |
3)线程和“主内存”、“工作内存”的关系 |
当线程需要读取共享变量时,会从“主内存”拷贝至“工作内存”,再从“工作内存”读取。 当线程需要修改共享变量时,会先修改“工作内存”中的数据副本,再将数据同步回“主内存”。 |
1.2 内存可见性演示
什么是内存可见性? |
内存可见性是指,线程对共享变量值的修改,能否被其他线程及时察觉。 如果一个线程修改了共享变量值,但没有及时写回内存中,导致其他线程无法获得已修改的正确数据,这就被认为出现了线程安全问题。 |
public class Volatile_Demo0 {
//有一个共享变量flag,注意该变量没有被 volatile 修饰;
public static int flag = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程,线程中当flag为0时,一直循环判断;
Thread t = new Thread(()->{
while (flag == 0){}
});
//启动线程;
System.out.println("run开始");
t.start();
//让main线程休眠两秒后,将flag的值改为1;
Thread.sleep(2000);
flag = 1;
//让main线程等待t线程结束;
t.join();
System.out.println("run结束");
}
}
//运行结果:
run开始
...
程序一直在执行,没有打印“run结束”。
出现了线程安全问题。
程序无法结束的原因是什么? |
根据代码,flag 在线程启动两秒后被改为 1 ,此时 t 线程应该因为跳出 while 循环而执行完毕。 正如上文“线程和‘主内存’、‘工作内存’的关系”中提到的,线程读取共享数据到“工作内存中”,再从“工作内存”读取数据。 所以此时在 t 线程中,参与 while 循环条件判断的 flag ,实际上是一个存储在“工作内存”的 flag 副本。 |
二、指令重排序
1)什么是指令重排序? |
2)指令重排序存在什么问题? |
指令重排序的前提是“保证逻辑不变”。这一点在单线程环境下较容易判断,但是在多线程环境下,代码复杂程度高,编译器在编译阶段对代码执行效果的判断存在困难。 |
三、关键字 volatile
1)volatile 的作用是什么? | |
<1> | |
<2> | 禁止指令重排序。volatile 修饰的变量读写操作的相关指令不允许被重排序。 |
2)内存可见性和指令重排序都是编译器优化,怎么好像都是负作用? |
3)volatile 不保证原子性 |
volatile 和 synchronized 有本质的区别,synchronized 保证原子性,而 volatile 保证的是内存可见性。 |
4)合理的使用 volatile 关键字 |
编译器优化就好像一场激烈的风暴,而程序员要做的就是掌控这场风暴,必要时让风暴停一停。 为此,Java 提供了 volatile 关键字供程序员使用。当使用 volatile 关键字时,强制读写内存,禁止指令重排序,程序运行速度变慢,但数据准确性提高,线程变得安全了。 |
代码演示 volatile 的使用效果,沿用上文“内存可见性演示”中的代码:
public class Volatile_Demo0 {
//有一个共享变量flag,注意该变量已经被 volatile 修饰;
public volatile static int flag = 0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程,线程中当flag为0时,一直循环判断;
Thread t = new Thread(()->{
while (flag == 0){}
});
//启动线程;
System.out.println("run开始");
t.start();
//让main线程休眠两秒后,将flag的值改为1;
Thread.sleep(2000);
flag = 1;
//让main线程等待t线程结束;
t.join();
System.out.println("run结束");
}
}
//运行结果:
run开始
run结束
与上文“内存可见性演示”中的代码唯一的不同,就是在共享变量 flag 上,使用了 volatile 进行修饰。
但这次的结果是程序正常执行完毕,证明了 volatile 的作用。
原文地址:https://blog.csdn.net/zzy734437202/article/details/134757070
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_32596.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!