zoukankan      html  css  js  c++  java
  • JAVA内存模型(JMM简述)

    Java内存模型(Java Memory Model)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保证效果一致的机制及规范

    JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内用于存储线程私有的数据,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行
    首先要将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,工作内存和主内存 传值过程由JMM控制。
    在JMM中主内存属于共享数据区域,从某个程度上讲应该包括了堆和方法区,而工作内存数据线程私有数据区域
    从某个程度上讲则应该包括程序计数器、虚拟机栈以及本地方法栈。我们可能会看见主内存被描述为堆内存,工作内存被称为线程栈,实际上他们表达的都是同一个含义
    主内存主要存储的是Java实例对象,所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量),当然也包括了共享的类信息、常量、静态变量。由于是共享数据区域,多条线程对同一个变量进行访问可能会发现线程安全问题。
    工作内存
    主要存储当前方法的所有本地变量信息(工作内存中存储着主内存中的变量副本拷贝),每个线程只能访问自己的工作内存,即线程中的本地变量对其它线程是不可见的,线程间无法相互访问工作内存,因此存储在工作内存的数据是线程安全的
    存储在主内存不管它是基本数据类型或者包装类型(Integer、Double等)还是引用类型,都会被存储到堆区
    JMM
    线程 - 》cpu->cpu寄存器->cpu缓存-》主内存
    executor->n线程->n内核线程->线程调度器-》cpu
    Java内存模型和计算机硬件内存架构是一个相互交叉的关系,是一种抽象概念划分与真实物理硬件的交叉

    指令重排
    计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,一般分以下3种
    1、编译器优化的重排
    编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。
    2、指令并行的重排
    现代处理器采用了指令级并行技术来将多条指令重叠执行。如果不存在数据依赖性(即后一个执行的语句无需依赖前面执行的语句的结果),处理器可以改变语句对应的机器指令的执行顺序
    3、内存系统的重排
    由于处理器使用缓存和读写缓存冲区,这使得加载(load)和存储(store)操作看上去可能是在乱序执行,因为三级缓存的存在,导致内存与缓存的数据同步存在时间差。

    其中编译器优化的重排属于编译期重排,指令并行的重排和内存系统的重排属于处理器重排,在多线程环境中,这些重排优化可能会导致程序出现内存可见性问题

    如执行ADD指令时 写到内存时没有完成 后面计算就无法进行,停顿会造成CPU性能下降,因此我们应该想办法消除这些停顿,这时就需要使用到指令重排了,既然ADD指令需要等待,那我们就利用等待的时间做些别的事情。

    A线程调用写入方法,而B线程调用读取方法
    指令重排只会保证单线程中串行语义的执行的一致性,但并不会关心多线程间的语义一致性

    在并发编程模式中,势必会遇到上面三个概念
    1、可见性
    可见性指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值
    对于串行程序来说,可见性是不存在的在多线程环境中线程A修改了共享变量x的值,还未写回主内存时,另外一个线程B又对主内存中共享变量x进行操作。
    2、有序性
    有序性是指对于单线程的执行代码,我们总是认为代码的执行是按顺序依次执行的,但对于多线程环境,则可能出现乱序现象,因为程序编译成机器码指令后可能会出现指令重排现象,重排后的指令与原指令的顺序未必一致多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的
    3、原子性
    一个操作或者多个操作要么全部执行要么全部不
    执行。
    JMM提供的解决方案:
    理解了原子性,可见性以及有序性问题后,看看JMM是如何保证的,对于方法级别或者代码块级别的原子性操作,可以使用synchronized关键字或者重入锁(ReentrantLock)保证程序执行的原子性
    而工作内存与主内存同步延迟现象导致的可见性问题,可以使用synchronized关键字或者volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见 对于指令重排导致的可见性问题和有序性问题,则可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化


    仅靠sychronized和volatile关键字来保证原子性、可见性以及有序性,那么编写并发程序可能会显得十分麻烦
    volatile在并发编程中很常见
    1、当一个线程修改了一个被volatile修饰共享变量的值,新值总数可以被其他线程立即得知
    2、禁止指令重排序优化
    volatile禁止重排优化:
    内存屏障,又称内存栅栏,是一个CPU指令,它的作用有两个,一是保证特定操作的执行顺序,
    二是保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)。由于编译器和处理器都能执行指令重排优化。
    如果在指令间插入一条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,
    Memory Barrier的另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读取到这些数据的最新版本

    群交流(262200309)
  • 相关阅读:
    桟错误分析方法
    gstreamer调试命令
    sqlite的事务和锁,很透彻的讲解 【转】
    严重: Exception starting filter struts2 java.lang.NullPointerException (转载)
    eclipse 快捷键
    POJ 1099 Square Ice
    HDU 1013 Digital Roots
    HDU 1087 Super Jumping! Jumping! Jumping!(动态规划)
    HDU 1159 Common Subsequence
    HDU 1069 Monkey and Banana(动态规划)
  • 原文地址:https://www.cnblogs.com/webster1/p/12246178.html
Copyright © 2011-2022 走看看