内存可见性也是造成线程安全问题的原因之一
一.什么是可见性?
可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到.
所以内存安全性问题就是一个线程对共享变量值的修改 其他线程不能够看到
看下面一串代码
public class Demo {
public static int flag = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (flag == 0) {
//这里什么也不做
}
System.out.println("thread1 线程结束");
});
Thread thread2 = new Thread(() -> {
//针对flag进行修改
Scanner scanner = new Scanner(System.in);
System.out.println("输入要修改的flag值:");
flag = scanner.nextInt();
});
thread1.start();
thread2.start();
}
}
以上代码线程1会在线程2修改之前flag一直等于0,while会无限循环, 线程2对flag进行输入修改,按理来所当线程2修改完flag之后线程2结束,线程1也会跳出循环线程结束
但是真正的结果↓

可以看到无论输入什么,程序都不会终止
通过JDK的jconsole来看线程的状态


可以看到线程2已经结束 线程1的状态仍然在运行 意思也就是while让然还在循环 线程2对flag值的修改并被影响到线程1 很明显这就是一个bug 线程安全问题,一个线程读取,一个线程修改,修改线程的值,并没有被读取的线程读取到
这就是内存可见性问题

上述JMM模型可见上篇文章方便后续理解
二.出现的原因
其实就是编译器优化再搞鬼
我们写的的代码 编译器把.java文件编译成.class字节码文件 再到JVM中运行
研究JDK的开发人员 希望通过让编译器,JVM,甚至是操作系统之间对程序员写的代码自动进行优化,会在你原有的逻辑不变的情况下,对你的代码进行调整,使程序的效率提高,在日常开发中提升的效率是明显可见的,也是非常有必要的
编译器虽然是优化操作,是能够保证逻辑不变,尤其是在多线程的程序中,编译器的判断可能出现失误,就可能导致编译器的优化,使优化后的逻辑和优化前的逻辑出现细节上的偏差 上述代码就是这种情况
分析代码:
JVM对于while循环执行这么多次的flag的操作 发现始终都是0既然结果都一样既然还要反复执行那么多次,没有必要,就把读读取内存的操作优化成读取寄存器这样的操作(把内存的值读取到寄存器中,获取再load(将数据从内存加载到寄存器)不再重新读内存,直接从寄存器里来取)
于是等到很多秒后,用户真正的输入新的值,真正修改flag,此时t1线程就感知不到(编译器优化是的线程1的读取操作不是真正的内存读取)
三.解决问题
解法1:

在while循环中加入sleep
本来while循环转的飞起,1s中几千万次,上亿次 但是加上sleep(1)之后循环次数就会大幅度降低,引入sleep之后,sleep消耗的时间相比于上面的load flag的操作就高了不知道多少 sleep占用1ms此时优化与不优化flag无足轻重
加上sleep由于sleep消耗的时间远大于load flag的操作 就没有必要优化 所以不会产生内存可见性问题 这是一种解决方法 但是太鸡肋了 sleep太消耗性能了

方法二:使用volatile关键字
volatile 修饰的变量, 能够保证 "内存可见性".
代码在写入 volatile 修饰的变量的时候,
• 改变线程工作内存中volatile变量副本的值
• 将改变后的副本的值从工作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候,
• 从主内存中读取volatile变量的最新值到线程的工作内存中
• 从工作内存中读取volatile变量的副本
简单来说:
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了. 线程2修改的数据 线程1就能及时看到了
private volatile static int flag2 = 0;
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
while (flag2 == 0) {
}
System.out.println("thread1 线程结束");
});
Thread thread2 = new Thread(() -> {
//针对flag进行修改
Scanner scanner = new Scanner(System.in);
System.out.println("输入要修改的flag值:");
flag2 = scanner.nextInt();
});
thread1.start();
thread2.start();
}

可见都能解决问题
volatile只能解决可见性问题 至于原子性问题还是要用synchronized(锁)来解决
268

被折叠的 条评论
为什么被折叠?



