JMM Java 内存模型

Java 内存模型

Java Memory Model , 为 java 内存模型, 简称为 JMM .

参考链接

深入理解java内存模型系列文章

主要参考为上述链接,上述讲的特别好,很清楚,很详细。

JMM 解决可见性的问题(同步包括独占性和可见性)

Java 内存模型的主要目的是定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量。

两个名词含义:

主内存: 线程之间共享变量存储在主内存中

工作内存: 每个线程都有一个私有的本地内存, 称为工作内存,里面存放着该线程以读/写共享变量的副本.

JMM 同时定义了线程和主内存之间的抽象关系.

  1. JMM 通过控制主内存与每个线程的本地内存之间的交互, 提供了内存可见性保证。

  2. Java 线程间的通信由 JMM 控制

    JMM 决定一个线程对共享变量的写入何时对另一个线程可见

  3. Java 内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存(与cache类比)

    线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,而不能直接读写内存中的变量。

    Java 线程 <-----> 工作内存 <------> savaload 操作 <-----> 主内存

主内存和工作内存之间的具体交互协议

Java 内存模型定义了一下八种操作:

  1. lock : ----> 主内存变量, 把一个变量标识为一条线程独占状态
  2. Unlock : ----> 主内存变量, 释放
  3. read : ----> 主内存变量, 把一个变量值从内存传送到线程的工作内存 ,便于load 操作
  4. load : ----> 工作内存的变量,把read的变量值放入到工作内存的变量副本中
  5. use : ----> 工作内存的变量,工作内存的变量值--> 执行引擎
  6. assign : ----> 工作内存的变量, 把从执行引擎得到的值----> 赋给工作内存的变量
  7. store : ----> 工作内存的变量, 传给主内存,便于后面的write操作
  8. write : ----> 主内存的变量,把从工作内存的传过来的变量进行write进主内存

Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行

happens-before 原则

JMM 中,如果一个操作执行的结果需要对另外一个操作可见,那么这两个操作之间必需要存在 happens-before 关系.

上述两个操作,可在同一个线程里,也可在不同的线程中.

规则如下:

  1. 程序顺序规则:

    一个线程中的每个操作,happens- before 作用于该线程中的任意后续操作.

  2. 监视器规则

    对一个监视器锁的解锁,happens- before 作用于随后对这个监视器锁的加锁.

  3. volatile 变量原则

    对一个 volatile 域的写,happens- before 作用于任意后续对这个 volatile 域的读

  4. 传递性

    如果 A happens- before B,且 B happens- before C,那么 A happens- before C.

happens- before 的理解

两个操作之间存在 happens- before, 并不是意味着 前一个操作必需在后一个操作执行之前执行, 而是仅仅要求前一个操作的结果(执行后的结果),对后一个操作可见, 且前一个操作按顺序排在第二个操作之前(代码间存在重排序问题).

重排序

源代码----> 编译器优化重排序 ----> 指令级并行重排序 -----> 内存系统重排序 ----> 最终执行的指令序列

为了保证内存的可见性,Java编译器在生成指令序列的适当位置会插入内存屏障指令来禁止特定类型的处理器重排序 , 因为重排序可能会导致多线程程序出现内存可见性问题。

JMM 特点

  1. Java内存模型JMM 确保在不同的编辑器和不同的处理器平台上,通过禁止特定的的编译器重排序和处理器重排序,为程序员提供一致的内存可见性。

  2. Java内存模型JMM 不会破坏已有的 数据依赖性as-if-serial 语义程序顺序规则. (具体含义见下面)

Java内存模型把内存屏障分为LoadLoad、LoadStore、StoreLoad和StoreStore四种:

image

数据依赖性

编译器和处理器在重排序时,会遵守数据依赖性。

  • 数据依赖性仅针对单个处理器中执行的指令序列和单个线程中执行的操作,不同处理器之间和不同线程之间的数据依赖性不被编译器和处理器考虑

as-if-serial 语义

不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
编译器,runtime 和处理器都必须遵守as-if-serial语义.

happens- before 程序顺序规则

JMM仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前

JMM允许不影响操作结果的重排序

重排序对多线程的影响

  • 多线程程序中,对存在控制依赖的操作重排序,可能会改变程序的执行结果。

  • 单线程程序中, 对存在控制依赖的操作重排序,不会改变执行结果。

数据竞争与顺序一致性保证

当程序未正确同步时,就会存在数据竞争

在Java内存模型对数据竞争的定义如下:

  1. 在一个线程中写一个变量
  2. 在另一个线程中读同一个变量
  3. 写和读没有通过同步来排序

如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序,正确同步的多线程程序

JMM 对正确同步的多线程程序做了如下保证:

  • 如果程序是正确同步的,则程序的执行将具有顺序一致性, 即程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同,

顺序一致性内存模型

顺序一致性内存模型 是一个被计算机科学家理想化了的理论参考模型,提供了极强的内存可见性保证

与Java 内存模型不同
特性:

  • 一个线程中的所有操作必须按照程序的顺序来执行
  • (不管程序是否同步)所有线程都只能看到一个单一的操作执行顺序
  • 在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

同步机制 :

volatile , synchronized, final

  1. volatile 关键字

    volatile修饰的变量,线程在每次使用变量的时候,都会读取修改后的最新的值.

    对变量的写操作,不依赖于当前值

    不能和其他变量同时出现在一个表达式中

    volatile的作用就是使它修饰的变量的读写操作都必须在内存中进行

    • 可见性:对一个 volatile 变量的读,总是能看到(任意线程)对这个 volatile 变量最后的写入.
    • 原子性:对任意 volatile变量的读/写都具有原子性

    volatile 变量的写-读可以实现线程之间的通信, 如下:

    • 线程A写一个volatile变量, 实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所在修改的)消息
    • 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息
    • 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息

    volatile 变量的写-读 的内存意义

    • 当写一个 volatile 变量时, JMM 会把该线程对应的本地内存中的共享变量刷新到主内存;

    • 当读一个 volatile 变量时, JMM 会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量.

    为了达到上述的内存意义效果,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序, 如下,采用保守策略的 JMM 内存屏障插入策略:

    • 在每个 volatile 写操作的前面插入一个 StoreStore 屏障
    • 在每个 volatile 写操作的后面插入一个 StoreLoad 屏障
    • 在每个 volatile 读操作的后面插入一个 LoadLoad 屏障
    • 在每个 volatile 读操作的后面插入一个 LoadStore 屏障
  1. synchronized 关键字

    线程同步的内部机制,通过加锁的方式保证线程的可见性和互斥性,
    即一个线程的执行结果可以被另一个线程所看到且在同一时间只能有一个线程执行被保护的代码块.

    锁释放和获取的内存语义:

    • 当线程释放锁时,JMM 会把该线程对应的本地内存中的共享变量刷新到主内存中

    • 当线程获取锁时,JMM会把该线程对应的本地内存置为无效,从而使得被监视器保护的临界区代码必须要从主内存中去读取共享变量

    如下消息传递:

    • 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息;
    • 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息;
    • 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息
  2. final 关键字

    与前面的锁和volatile相比较,对final域的读和写更像是普通的变量访问

    • 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。
    • 初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序

上面的两条规则其实可分为

1.写final域的重排序规则

  1. 读final域的重排序规则

final 域的重排序规则

  • JMM禁止编译器把final域的写重排序到构造函数之外

  • 编译器会在 final 域的写之后,构造函数 return 之前,插入一个 StoreStore 屏障(内存屏障)
    这个屏障禁止处理器把 final 域的写重排序到构造函数之外。

final 域的重排序规则

  • 在一个线程中,初次读对象引用与初次读该对象包含的 final 域,JMM 禁止处理器重排序这两个操作
    仅针对与处理器(重排序的后两种),
    编译器会在读final域操作的前面插入一个 LoadLoad 屏障

volatile与synchronized 的同异

  1. volatile 本质是在告诉 jvm 当前变量在寄存器中的值是不确定的,需要从主存中读取。
  2. synchronized 则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
  3. volatile 仅能使用在变量级别, synchronized 则可以使用在变量,方法.
  4. volatile 仅能实现变量的修改可见性,但不具备原子特性,而 synchronized 则可以保证变量的修改可见性和原子性
  5. volatile 不会造成线程的阻塞,而 synchronized 可能会造成线程的阻塞.
  6. volatile 标记的变量不会被编译器优化,而 synchronized 标记的变量可以被编译器优化.

如有错误,请指出,谢谢.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 并发系列的文章都是根据阅读《Java 并发编程的艺术》这本书总结而来,想更深入学习的同学可以自行购买此书进行学习。...
    小之丶阅读 1,035评论 1 7
  • 59. Spiral Matrix II: 这道题比上一个spiral matrix1好做,只要记录一下每一次的t...
    健时总向乱中忙阅读 171评论 0 0
  • 高效的晨读后郭老师用短短的几分钟时间跟同学们说了一下今天的安排,让每一个学生知道全体要做的事情,让他们可以见缝插针...
    山茶_5c91阅读 217评论 0 1