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

    }
}

执行结果:

相关推荐
n北斗2 分钟前
常用类晨考day15
java
骇客野人5 分钟前
【JAVA】JAVA接口公共返回体ResponseData封装
java·开发语言
yuanbenshidiaos1 小时前
c++---------数据类型
java·jvm·c++
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇25】C#面向对象动态多态——virtual、override 和 base 关键字、抽象类和抽象方法
java·开发语言·unity·c#·游戏引擎
Lojarro1 小时前
【Spring】Spring框架之-AOP
java·mysql·spring
莫名其妙小饼干1 小时前
网上球鞋竞拍系统|Java|SSM|VUE| 前后端分离
java·开发语言·maven·mssql
isolusion2 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp2 小时前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob2 小时前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder3 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试