JavaEE 第6节 内存可见性问题以及解决方法

目录

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

1、问题代码演示

2、基础知识铺垫

1)硬件层面

2)模型层面(JMM)

二、内存可见性问题的原因

三、volatile解决内存可见性问题


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

1、问题代码演示

java 复制代码
public class Threads {
    static int count = 0;

    //实现加锁
    private synchronized static void add() {
        count++;
    }
    public static Object object1=new Object();
    public static Object object2=new Object();
    public static int n=0;
    public static void main(String[] args) throws InterruptedException {

      Thread thread1=new Thread(()->{
          while(n==0){
              //什么都不做
          }
     System.out.println("while循环结束");
      });
      Runnable runnable=new Runnable() {
          @Override
          public void run() {
              System.out.println("修改n的值为非0");
              Scanner scanner=new Scanner(System.in);
              n=scanner.nextInt();
              System.out.println("修改完成");
          }
      };
      Thread thread2=new Thread(runnable);

      thread1.start();
      thread2.start();
      thread1.join();
      thread2.join();

    }

}

按照代码逻辑,我们在终端输入一个1,那么就会打印"while循环结束",但实际情况真是如此吗?

请看执行结果:

在输入一个1之后,程序并没有结束(正方形红点),那么就说明这个程序是有bug的。

2、基础知识铺垫

1)硬件层面

想要理解这个bug产生的原因,需要简单了解计算机的存储结构以及它们的特性:

寄存器(Register):访问速度最快,但存储的数据最少,掉电后数据丢失。

高速缓存(Cache):访问速度中等,存储数据中等,掉点后数据丢失。

硬盘(Hard Drive):访问速度最慢,存储数据最多,掉点后数据不会丢失。


Cache和Register都是CPU上的一部分,Cache还可以细分成多级缓存。

Register\Cache\Hard Drive这三者之间访问速度和存储大小差距是非常大的 ,都是按照数量级进行换算的,这就导致不同的硬件访问数据的速度会有质的不同。

2)模型层面(JMM)

Java内存模型 (Java Memory Model,JMM )是Java虚拟机 (JVM)规范的一部分。它规定了多线程环境下,每个线程访问共享内存的方式,这个模型的主要目标是保证程序员能够在多线程环境下编写出正确高效的程序。

接下来用一张图描述JMM的具体要求:

这里的本地内存指的就是Working Memory,相当于刚才硬件部分介绍的寄存器(Register)以及高速缓存(Cache)。主内存就是Main Memory,相当于刚才硬件部分介绍到的硬盘(Hard Drive)。

在JMM的控制下,线程与线程使用的内存不是物理层面的共享,他们都分别从Main Memory的共享内存区域,拷贝一份副本到自己的Working Memory。

二、内存可见性问题的原因

从刚才的图示中可以知道,想要实现两个线程t1,t2之间的通信需要两步:

1.t1修改了对应变量然后把Working Memory同步更新会Main Memory。

2.t2从Main Memory更新Working Memory保证使用的数据是正确的。

还记得刚才的代码吗?

t1的while循环中什么都没有写,因此这个空循环的执行速度是非常快的 ,可能一秒执行几千甚至上万次。这时编译器看不下去了,因为n这个变量是在Main Memory上的(每次的读取速度非常慢),编译器为了优化代码,让t1直接去自己的Working Memory去读取变量n,进而提高代码的执行效率,但这样就会导致内存可见性问题:

三、volatile解决内存可见性问题

volatile的其中一个英文意思是:易变的。

也就是说这个关键字修饰的变量,可以告诉编译器不要在Working Memory上读 ,那里不准确,必须在硬盘上(Main Memory)上读这个数据,因为这个数据很容易改变,一旦改变,Working Memory上有没有及时更新,会产生问题的!

具体用法:

java 复制代码
public class Threads {
    static int count = 0;

    //实现加锁
    private synchronized static void add() {
        count++;
    }
    public static volatile int n=0;//代码只在这里多加了volatile修饰变量
    public static void main(String[] args) throws InterruptedException {

      Thread thread1=new Thread(()->{
          while(n==0){
              //什么都不做
          }

          System.out.println("while循环结束");
      });
      Runnable runnable=new Runnable() {
          @Override
          public void run() {
              System.out.println("修改n的值为非0");
              Scanner scanner=new Scanner(System.in);
              n=scanner.nextInt();
              System.out.println("修改完成");
          }
      };
      Thread thread2=new Thread(runnable);

      thread1.start();
      thread2.start();
      thread1.join();
      thread2.join();

    }
}

执行结果:

相关推荐
小蒜学长21 分钟前
校园社团招新网站的设计与实现(代码+数据库+LW)
java·数据库·spring boot·后端·校园社团招新网站
Dovis(誓平步青云)1 小时前
多个链接不等于多份证据,新闻核验看板怎样合并来源
java·服务器·前端·javascript·人工智能·pdf·电脑
旺仔Sec1 小时前
2026年江西省职业院校技能大赛(中职组)应用软件系统开发赛项竞赛样题
java·应用软件系统开发
Dovis(誓平步青云)2 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
一条破秋裤2 小时前
Linux 线程同步:读写锁
java·linux·jvm
蜗牛互联网2 小时前
从Barclays扩大Claude部署看受监管软件工程的责任重分配
java·大数据·人工智能·后端·软件工程
天远API2 小时前
零信任架构实战:基于天远股权穿透构建自动化供应链授信穿透网关
java·人工智能·架构·自动化
最强小杰2 小时前
claude-opus-5.5 API 接入教程:anthropic-version 头、tools 定义、流式事件格式三个常见问题全部梳理,收藏备用
java·服务器·网络·ai
Cc.Y2 小时前
Java零基础入门:方法(函数)深度掌握
java·开发语言
奋发向前wcx3 小时前
CSP-J复赛模拟赛1 王晨旭补题 2026.10.2
java·开发语言