内存可见性引发的思考

简介: 内存可见性引发的思考

正文


内存可见性:


在并发的程序中,我们时不时会用到共享的变量,用这些变量控制我们整体业务逻辑的行为。

运行下面的程序:


public class ShareThread {
  private static boolean isRun = true;
  public static class MyThread extends Thread {
    @Override
    public void run() {
      while(isRun) {
        // do other things 
      }
    }
  }
  public static class StopThread extends Thread {
    @Override
    public void run() {
      try {
        // 自己休眠2s 执行权限让给其他人
        Thread.sleep(2000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      // 结束 MyThread 的运行
      isRun=false;
      // do other things 
    }
  }
  public static void main(String[] args) throws Exception{
    // 创建线程
    MyThread myThread = new MyThread();
    // 创建结束线程
    StopThread stopThread = new StopThread();
    // 启动线程
    myThread.start();
    stopThread.start();
    // 主线程    等待线程执行完
    myThread.join();
    stopThread.join();
  }
}


执行上面的线程,得到的结果是:2s后,线程还没有结束执行。

StopThread这个线程中明明isRun=false;应当结束啊,为什么呢?


刚才问题的思考? 


就在上面,定义了2个线程:

1、一个线程执行业务逻辑;

2、另一个线程负责控制第一个业务的执行。

但是,感觉没有任何的疑问啊,为啥没有停止呢?

有可能大家都是到 我们加一个关键字就好啦:volatile,这样就实现最终的效果。


计算机实现可见性


我们编写代码都是使用的高级语言,就像我们上面写的程序,它实质的执行过程是:编译-> 加载-> 验证->准备->解析->初始化->使用->卸载。这一套整体的过程。

真正执行的是机器码在计算机中执行。

实现内存的可见性,首先就要了解计算机的运行,如何实现缓存一致性的。



缓存一致性协议


首先了解一下计算机的体系结构:

计算机在执行计算的所使用的数据不是直接从内存中取出来的,而是先校验高速缓存中是否存在这样的数据,如果有,直接返回;如果没有,那么就会读取内存中的数据。


CPU为什么需要高速缓存?

首先,肯定是提高工作效率,其次是缓解CPU的执行速度与内存读取时间不匹配的问题,是一个时间的问题。


局部性原理:

时间局部性:如果一个信息项正在被访问,那么在近期它很可能还会被再次访问,因为程序中存在着循环、递归、方法的反复调用等。

空间局部性:如果一个存储器的位置被引用,那么将来他附近的位置也会被引用。因为程序中顺序执行的代码、连续创建的两个对象、数组等。

这一介绍一个多核CPU的缓存一致性协议MESI:

由于我们的计算机体系结构是:

CPU<->高速缓存<->控制总线、数据总线<->内存<->硬盘


在高速缓存中,每一个cacheline都会有一个状态的标志,占用2bit,MESI就是这些状态的英文单词首字母的缩写。

M(Modified):该Cache line有效,数据被修改了,和内存中的数据不一致,数据只存在于本Cache中。

E(Exclusive):该Cache line有效,数据和内存中的数据一致,数据只存在于本Cache中。

S(Shared):该Cache line有效,数据和内存中的数据一致,数据存在于很多Cache中。

I(Invalid): 该Cache line无效。


在多核CPU中,每一个CPU都会拥有自己的高速缓存以及相应的其他的硬件。

5.png


根据缓存一致性的协议:

当某一个CPU读取后,会将这cachekine标志变为E,

当其他的CPU读取相同的数据的时候,这个时候会将这个状态变成:S状态。

某一个CPU修改的时候,修改的这个cache变成M,其他引用的cache数据变成I状态。

同步数据:其他的CPU读取当前CUP修改的数据,会通知当前的CPU将数据写会在主存,当前的状态变成E,之后,其他的CPU会读取当前的数据,读取完成之后,会将所有的状态变成S状态。

经过这样的步骤,实现了不同的CUP的数据的可见性。


缓存一致性的问题:想必大家看到上面的过程有可能会感觉步骤这么多。对的,这个一致性是非常消耗时间的。


Java代码中实现内存可见性


在开头,我们的程序加上:volatile关键字就可以保证程序的正确执行,这是因为volatile关键字可以实现内存的可见性以及禁止指令的重排序


实现内存的可见性关键技术:内存屏障以及先发生原则(Happen-Before)

内存屏障:

内存屏障分为:读屏障、写屏障。

先发生原则:

程序顺序原则:一个线程内保证语义的串行性

volatile规则:volatile 变量的写,先发生于读,这保证了volatile变量的可见性

锁规则:解锁(unlock)必然发生在随后的加锁(lock)前

传递性:A先于B,B先于C,那么A必然先于C

线程的start()方法先于它的每一个动作

线程的所有操作先于线程的终结(Thread.join())

线程的中断(interrupt())先于被中断线程的代码

对象的构造函数执行,结束先于finalize() 方法

这样的约束,实现了volatile关键字可以实现内存的可见性以及禁止指令的重排序,保证了程序的正常运行。

相关文章
|
4月前
|
安全 Java 开发者
探索Java内存模型:可见性、有序性和并发
在Java的并发编程领域中,内存模型扮演了至关重要的角色。本文旨在深入探讨Java内存模型的核心概念,包括可见性、有序性和它们对并发实践的影响。我们将通过具体示例和底层原理分析,揭示这些概念如何协同工作以确保跨线程操作的正确性,并指导开发者编写高效且线程安全的代码。
|
4月前
|
缓存 安全 Java
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
Java面试题:解释volatile关键字的作用,以及它如何保证内存的可见性
77 4
|
4月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
78 1
|
4月前
|
安全 Java 开发者
Java面试题:Java内存模型解析,Java内存模型的基本概念和它的重要性,Java内存模型中的“可见性”和“有序性”,以及具体实现?
Java面试题:Java内存模型解析,Java内存模型的基本概念和它的重要性,Java内存模型中的“可见性”和“有序性”,以及具体实现?
56 1
|
5月前
|
缓存 Java 程序员
Java内存模型深度解析:可见性、有序性和原子性
在多线程编程中,正确理解Java内存模型对于编写高效且无bug的并行程序至关重要。本文将深入探讨JMM的三大核心特性:可见性、有序性和原子性,并结合实例分析如何利用这些特性来避免常见的并发问题。
59 1
|
4月前
|
存储 缓存 安全
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
Java面试题:介绍一下jvm中的内存模型?说明volatile关键字的作用,以及它如何保证可见性和有序性。
36 0
|
4月前
|
Java 开发者
Java面试题:解释Java内存模型中的内存可见性,解释Java中的线程池(ThreadPool)的工作原理,解释Java中的CountDownLatch和CyclicBarrier的区别
Java面试题:解释Java内存模型中的内存可见性,解释Java中的线程池(ThreadPool)的工作原理,解释Java中的CountDownLatch和CyclicBarrier的区别
26 0
|
4月前
|
存储 安全 Java
Java面试题:Java内存模型中的主内存与工作内存是如何协同工作的?请解释Java内存模型中的可见性、原子性和有序性,举例说明Java内存模型中的happens-before关系
Java面试题:Java内存模型中的主内存与工作内存是如何协同工作的?请解释Java内存模型中的可见性、原子性和有序性,举例说明Java内存模型中的happens-before关系
36 0
|
5月前
|
存储 缓存 Java
【Java并发基础】Java内存模型解决有序性和可见性
【Java并发基础】Java内存模型解决有序性和可见性
|
存储 缓存 SpringCloudAlibaba
JUC并发编程(一):Java内存模型(JMM)及三大特性:可见性、有序性、原子性
在当今高流量、高并发的互联网业务场景下,**并发编程技术**显得尤为重要,不管是哪一门编程语言,掌握并发编程技术是个人进阶的必经之路。时隔一个半月没有写技术博客文章,有点生疏了。。。闲话少叙,接下来我将围绕并发编程知识点进行总结讲解,这里从并发编程入门开始,讲述Java内存模型和并发的三大特性。
188 1
JUC并发编程(一):Java内存模型(JMM)及三大特性:可见性、有序性、原子性