栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA内存模型

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA内存模型

JAVA内存模型
  • 线程间通讯
  • jmm 八种操作
  • 有序性
    • 内存屏障
    • happens-before原则
  • 可见性
    • 总线嗅探机制
    • volatile
  • 原子性

线程间通讯

Java 里面进行多线程通信的主要方式就是共享内存的方式,而scala语言采用线程间发消息机制进行通信。
具体共享的内存,参考jvm 内存结构来看:

#JMM解决什么问题
JMM研究的是在多线程下Java代码的执行顺序,以及共享变量的读写。在多线程下,对共享变量读写的场景下,代码的执行结果可能和自己的期望结果不一致。为什么会产生这种现象呢?

  • 共享变量内存读写,具体计算机中存在多级缓存,寄存器,存在内存一致性问题。(JMM是对具体计算机内存模型的抽象,主要是主内存和工作内存的交互,定义自己的操作标准,基于具体计算机硬件支持,实现内存一致性,对开发人员来说可见。)

  • 处于性能的考虑,软件层次,JVM会对我们编写的代码进行优化,优化为更高效的代码指令;硬件层次,CPU还有Processor 优化和缓存优化。从而,造成以下三个问题:
    1.可见性问题
    2.原子性问题
    3.有序性问题

jmm 八种操作

lock:锁定。作用于主内存的变量,把一个变量标识为一条线程独占状态。
unlock:解锁。作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
read:读取。作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
load:载入。作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
use:使用。作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
assign:赋值。作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
store:存储。作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
write:写入。作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
借用一张图:

注:这里内存读取,写入失效都是以内存行为单位

有序性
  • 使用内存屏障,防止重排,
  • 定义执行顺序happens-before原则
内存屏障

LoadLoad
发生在两次读取操作之间,防止后面的读取操作重排序到屏障前面去。

LoadStore
发生读取和写入之间,防止后面的写入操作重排序到屏障之前

StoreStore
发生在两次连续的写之间,防止的写入操作重排序到屏障后面

StoreLoad
在线程切换时有效,让所有写入都同步到内存,同时读取的内容是内存中最新的数据。

LoadLoad + LoadStore = Acquire 即让同一线程内读操作之后的读写上不去,第一个 Load 能读到主存最新值
LoadStore + StoreStore = Release 即让同一线程内写操作之前的读写下不来,后一个 Store 能将改动都写入主存
StoreLoad 最为特殊,还能用在线程切换时,对变量的写操作 + 读操作做同步,只要是对同一变量先写后读,那么屏障就能生效

  • volatile 读操作的后面插入 LoadLoad 屏障和 LoadStore 屏障。
  • volatile 写操作的前后分别插入一个 StoreStore 屏障和一个 StoreLoad 屏障。
  • volatile 方式读写变量, Store-Load,与 LoadLoad 屏障最为有用,保证可见性,防止指令重排;
  • final修饰成员变量:会在构造方法结束前,添加StoreStore屏障,防止重排到构造方法外。
happens-before原则

1单线程happen-before原则:在同一个线程中,控制流(循环,分支)书写在前面的操作happen-before后面的操作。
2锁的happen-before原则:同一个锁的unlock操作happen-before此锁的lock操作。
3volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
4happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
5线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
6线程中断的happen-before原则:对线程interrupt方法的调用happen-before被中断线程的检测到中断发送的代码。
7线程终结的happen-before原则:线程中的所有操作都happen-before线程的终止检测。
8对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。

可见性

依赖具体计算的缓存一致性协议,使用 volatile 修饰共享变量后,每个线程要操作变量时会从主内存中将变量拷贝到本地内存作为副本,当线程操作变量副本并写回主内存后,会通过 CPU 总线嗅探机制告知其他线程该变量副本已经失效,需要重新从主内存中读取。

总线嗅探机制

在现代计算机中,CPU 的速度是极高的,如果 CPU 需要存取数据时都直接与内存打交道,在存取过程中,CPU 将一直空闲,这是一种极大的浪费,所以,为了提高处理速度,CPU 不直接和内存进行通信,而是在 CPU 与内存之间加入很多寄存器,多级缓存,它们比内存的存取速度高得多,这样就解决了 CPU 运算速度和内存读取速度不一致问题。

由于 CPU 与内存之间加入了缓存,在进行数据操作时,先将数据从内存拷贝到缓存中,CPU 直接操作的是缓存中的数据。但在多处理器下,将可能导致各自的缓存数据不一致(这也是可见性问题的由来),为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,而嗅探是实现缓存一致性的常见机制。

volatile

volatile的作用:

  • 单一变量的赋值原子性;
  • 保证线程内按屏障有序,线程切换时,按Happens before有序;
  • 线程切换时若发生了写 ->读,则变量可见,顺带影响普通变量的可见。
  • volatile 读操作的后面插入 LoadLoad 屏障和 LoadStore 屏障。
  • volatile 写操作的前后分别插入一个 StoreStore 屏障和一个 StoreLoad 屏障。
  • volatile 方式读写变量, Store-Load,与 LoadLoad 屏障最为有用,保证可见性,防止指令重排;
原子性

锁可以保证整个临界区代码的执行具有原子性。所以而锁可以保证整个临界区代码的执行具有原子性。所以在功能上,锁比volatile更强大;在性能上,volatile更有优势。

乐观锁多用于读多写少的环境,避免频繁加锁影响性能,加大了系统的整个吞吐量;而悲观锁多用于写多读少的环境,避免频繁失败和重试影响性能。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/397278.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号