多线程--内存可见性问题以及volatie关键字

内存可见性也是造成线程安全问题的原因之一

一.什么是可见性?

可见性指, 一个线程对共享变量值的修改,能够及时地被其他线程看到. ​

所以内存安全性问题就是一个线程对共享变量值的修改 其他线程不能够看到

看下面一串代码


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(锁)来解决

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值