关于volatile解决内存可见性问题(保证线程安全)

简介: 关于volatile解决内存可见性问题(保证线程安全)

Volatile是和内存可见性问题是密切相关的。先看下面一段代码,执行结果是什么?

class MyCount{
    public int flag = 0;
}
public class ThreadDemo15 {
    public static void main(String[] args) {
        MyCount myCount = new MyCount();
        Thread t1 = new Thread(()->{
            while (myCount.flag == 0){
                //循环体代码
            }
            System.out.println("t1 线程循环执行结束");
        });
        Thread t2 = new Thread(()->{
            Scanner sc = new Scanner(System.in);
            System.out.println("给flag赋予非0值");
            myCount.flag = sc.nextInt();
        });
        t1.start();
        t2.start();
    }
}

运行结果如下:


我们的预期是t2把flag改成非О的值之后, t1随之就结束循环了。但是,此时无论怎么给flag赋予非零值,t1线程一直处理循环没有结束,也就是说t1线程拿到的flag是0。


这种情况就是一种内存可见性问题,这段代码实际上是有bug的,涉及到一个线程读一个线程修改,是一个线程不安全问题。下面再来探究一下为什么t1线程总是拿到的是0:


8e1050173b474a3cb4a67a302dea189b.png

t1线程这里循环体的判断是要分成两个步骤的,先是load,把内存中flag的值读取到寄存器里,然后再是cmp,把寄存器的值和0进行比较,CPU针对寄存器的操作要比内存操作快3-4个数量级,计算机对于内存的操作比硬盘快3-4个数量级。根据比较结果,决定下一步往哪个地方执行。在t2线程真正修改flag值之前,t1线程的循环已经执行了很多次了,而且t1线程load的结果都是一样的。这里会涉及到编译器的优化问题,由于load是在内存中进行加载,执行的速度太慢了(相对于cmp来说),在加上反复load的结果是一样的,JVM就认为不用再重复的load了,认为flag的值没有修改,认为只读一次就好了,这就是编译器的一种优化方式。

一个线程针对一个变量进行读取操作,同时另一个线程针对这个变量进行修改,此时读到的值不一定就是修改后的值,这个读线程没有感知到变量的变化,这就是一种内存可见性问题。此时需要手动干预,可以给flag这个变量加上volatile关键字,这样每一次的读取操作都要重新读取到这个变量的内存内容,就不会进行优化操作了。

class MyCount{
    volatile public int flag = 0;
}

下面是来自于Java官方文档的对于内存可见性问题的解释:

从JMM(Java内存模型)的角度重新表述内存可见性问题:

Java程序里,有主内存,每个线程还有自己的工作内存。

t1线程进行读取的时候,只是读取了自己工作内存的值。

t2线程进行修改的时候,先修改的自己工作内存的值,然后再把工作内存的内容同步到主内存中。但是由于编译器优化,导致t1没有重新的从主内存同步数据到自己工作内存,读到的结果就是"修改之前”的结果。

在最开始的代码解释中,我只用了内存和寄存器两种概念,但是其实内存和寄存器之间的存储读取的速度差异实在是太大了,在他们中间还有一种高速缓存器cache,因为不同cup的cache不一样,所以应该是为了避免这种差异,统一叫做工作内存。


总结:volatile是不保证原子性的,原子性是靠synchronized来保证的。但是volatile和synchronized都能保证线程安全。在多线程中,针对同一个变量,一个线程进行读取,一个线程进行修改,那么加上volatile关键字可以保证线程安全问题。

 

相关文章
|
2月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
61 0
|
10天前
|
存储 缓存 Java
java线程内存模型底层实现原理
java线程内存模型底层实现原理
java线程内存模型底层实现原理
|
26天前
|
监控 Java 数据库连接
使用线程池时,如何避免内存泄漏的问题?
使用线程池时,如何避免内存泄漏的问题?
|
21天前
|
Arthas 监控 Java
监控线程池的内存使用情况以预防内存泄漏
监控线程池的内存使用情况以预防内存泄漏
|
21天前
|
监控 数据可视化 Java
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况
|
2月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
3月前
|
Java
JVM内存问题之jstack命令查看JVM线程快照如何解决
JVM内存问题之jstack命令查看JVM线程快照如何解决
|
2月前
|
存储 NoSQL Java
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的
Tair的发展问题之Tair对于不同存储介质(如内存和磁盘)的线程分配是如何处理的
|
3月前
|
存储 缓存 Java
(一) 玩命死磕Java内存模型(JMM)与 Volatile关键字底层原理
文章的阐述思路为:先阐述`JVM`内存模型、硬件与`OS`(操作系统)内存区域架构、`Java`多线程原理以及`Java`内存模型`JMM`之间的关联关系后,再对`Java`内存模型进行进一步剖析,毕竟许多小伙伴很容易将`Java`内存模型(`JMM`)和`JVM`内存模型的概念相互混淆,本文的目的就是帮助各位彻底理解`JMM`内存模型。
|
3月前
|
微服务
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么
多线程内存模型问题之在单例模式中,volatile关键字的作用是什么

热门文章

最新文章

下一篇
无影云桌面