目录
1 概述
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改之后的最新值。
2 案例
代码示例:
public class VisibilityDemo01 {
public static void main(String[] args) {
//1. 开启一个子线程
MyThread myThread = new MyThread();
myThread.start();
//2. 主线程执行
while (true) {
if (myThread.isFlag()){
System.out.println("主线程进入循环执行");
}
}
}
}
class MyThread extends Thread{
//成员变量
private boolean flag = false;
@Override
public void run() {
//模拟业务代码耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("flag="+flag);
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
运行结果:
可见,当子线程修改了变量的值时,主线程依然没有读取到修改之后的值。
3 JMM内存模型
JMM(Java Memory Model):java内存模型,是java虚拟机规范中所定义的一种内存模型,java内存模型是标准化的,屏蔽掉了底层不同计算机的区别。
java内存模型描述了java程序中各种变量(线程共享)的访问规则,以及在jvm中将变量存储到内存和从内存中读取变量这样的底层细节。
JMM有以下规定:
- 所有的共享变量都存储于主内存。这里的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。
- 每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本
- 线程对变量的所有操作(读、取)都必须在工作内存中完成,而不能直接读写主内存中的变量
- 不同线程之间也不能直接访问对方工作内存中的变量,线程之间变量的值的传递需要通过主内存中转来完成。
本地内存和主内存的关系:
问题分析:
因为成员变量是定义在类中,所以它的变量值存储在主内存中,
1)子线程从主内存读取到数据放入其对应的工作内存
2)将flag的值修改为true ,但是这个时候还没有写会到主内存
3)此时main方法读取到了flag的值为false(这就是上面模拟业务代码休眠一秒的原因,不然子线程速度太快值刚修改完就写回了主内存,那么main线程读取到的值就是最新的值了)
4)当子线程t将flag的值写回去后,但是main函数里的while(true)调用的是系统比较底层的代码,速度快,快到没有时间去读取主内存中的值。
所以while(true)读物到的值一直是false。(如果此时有一个时刻main线程从主内存读取到了主内存flag的最新值,那么if语句就可以执行,main线程何时从主内存中读取最新的值,我们无法控制)
可见性问题原因:所有的共享变量存于主内存中,每个线程有自己的本地内存,而且线程读写共享数据也是通过本地内存交换的,所以导致了可见性问题。
4 如何解决
那么既然已经知道了产生可见性问题的原因,那么如何解决它呢?
4.1 加锁
将上述主线程里面的代码改成如下:
//2. 主线程执行
while (true) {
synchronized (myThread){
if (myThread.isFlag()){
System.out.println("主线程进入循环执行");
}
}
}
运行结果:
发现可以读取到子线程修改之后的值。
原因:
1)线程获得锁
2)清空工作内存
3)将主内存拷贝共享变量最新值到工作内存中成为副本
4)执行代码
5)将修改后的副本的值刷新回主内存中
6)线程释放锁
4.2 volatile关键字
将子线程中的部分代码修改如下:
//成员变量
private volatile boolean flag = false;
1)子线程t从主内存读取到数据放入其对应的工作内存
2)将flag值更改为true,但是这个时候flag值还没有写回主内存
3)此时main方法读取到了flag的值为false
4)当子线程将flag的值写回去后,失效其他线程对此变量副本(volatile的作用)
5)再次对flag进行操作的时候线程会从主内存读取最多的值,放入到工作内存中。
总结:volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。
以上只是部分内容,为了维护方便,本文已迁移到新地址:volatile~多线程下变量不可见 – 编程屋