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();

    }
}

执行结果:

相关推荐
腥臭腐朽的日子熠熠生辉16 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
ejinxian18 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之23 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码43 分钟前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿
爱的叹息1 小时前
Java 连接 Redis 的驱动(Jedis、Lettuce、Redisson、Spring Data Redis)分类及对比
java·redis·spring
程序猿chen1 小时前
《JVM考古现场(十五):熵火燎原——从量子递归到热寂晶壁的代码涅槃》
java·jvm·git·后端·java-ee·区块链·量子计算
松韬2 小时前
Spring + Redisson:从 0 到 1 搭建高可用分布式缓存系统
java·redis·分布式·spring·缓存
绝顶少年2 小时前
Spring Boot 注解:深度解析与应用场景
java·spring boot·后端