Java - 主内存&工作内存?使用volatile解决内存可见性问题

本文解释了内存可见性问题的概念及产生原因,并通过一个具体示例展示了如何利用volatile关键字解决该问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 什么是内存可见性问题?

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

二、 主内存与工作内存?

 提到内存可见性,我们就可能会听到工作内存以及主内存这两个概念... 这俩是什么?它们和内存可见性问题又有什么联系? 

首先要明确:CPU是无法直接对内存中的数据进行计算的,cpu的计算分为三个步骤:

  • 从内存中读取数据存入到寄存器中
  • 在寄存器中进行计算
  • 将寄存器中计算的结果传回内存

JVM在设计的时候,对底层的CPU和内存的关系进行了一个抽象:

主内存:计算机真正的内存

工作内存:对应于CPU中的寄存器和缓存等等。由于有多核的存在,这个工作内存也会有多个存在。这个概念和线程私有的内存还是不一样的,线程私有的内存,其实是栈内存。

涉及到主存工作内存的问题,多半是和多线程在多个实际的CPU核心中运行的场景中才讨论的。

正是由于工作内存的存在,而且可能是分布在不同的CPU核心中,有了数据的“内存可见性问题

🔊  来看一个实例:

该代码中有两个线程,线程1进行循环操作,线程2控制线程1的循环。希望达到 “ 当用户输入非0数字时,线程1退出循环,退出进程” 的效果

import java.util.Scanner;

public class demo3 {
    public static int flag=0;
    public static void main(String[] args) {
        Thread thread1=new Thread(()->{
            while(flag==0){
                //执行循环,循环内部什么都不做
            }
            //一旦退出循环,打印循环结束
            System.out.println("循环结束");
        });
        thread1.start();

        Thread thread2=new Thread(()->{
            //让用户输入一个非0数字退出循环
            System.out.println("请输入一个非0数字,退出线程1的循环:");
            Scanner scanner=new Scanner(System.in);
            flag=scanner.nextInt();
        });

        thread2.start();
    }
}

代码结果:当用户输入非0数字时,进程并没有结束

原因分析:

线程1不断地进行读取/判断数据

由于从主内存中读取数据的过程耗费的时间>从工作内存(寄存器等)中读取数据的时间,当多次循环判断,操作系统/jvm编辑器发现每次写回主内存的数据并没有修改过的时候,就会进行优化:  既然反复读内存读到的数据都一样,干脆将读到的值存到寄存器中,下次直接读寄存器就好了。

于是经过优化后,线程1不再进行重复读

麻烦的事儿来了!如果此时的线程2对主内存中的数据进行了修改,线程1就没办法感受到主内存中数据的变化。这就是内存可见性问题

三、使用volatile解决问题

内存可见性问题本质上是操作系统/jvm对计算过程优化后带来的问题,我们在待修改的变量前使用volatile,其实相当于显示的禁止了这种优化。

在待修改的变量前加上 volatile ,是给这个相应的变量加上了“内存屏障”(特殊的二进制指令)。jvm在读取这个变量的时候,由于内存屏障的存在,就知道每次都需要重新从主内存中读取数据而不是草率的进行优化。

对刚刚的实例进行修改, 只需要在变量前增加volatile关键字:

 这一次,当我们输入非0数字的时候,进程就如我们所愿结束了...

不过:

如果我们在线程1的循环内存加上个sleep,编辑器好像就不会优化了,程序也能如愿结束。编辑器的优化是个玄学问题,什么时候会优化我们无从确定。为了保险起见,有必要的时候我们还是加上volatile吧 ~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值