java线程之内存模型


参考书籍: <<java并发编程的艺术>>
这篇文章是自己阅读该书籍时的读书笔记

1. 并发编程模型的两个关键问题

在并发模型中, 常常要处理两个关键的问题:

  • 线程之间如何通信
  • 线程之间如何同步
    通信 : 线程之间以何种机制来交换信息;
    同步: 线程中用于控制不同线程之间操作发生相对顺序的机制

常见的通信机制有两种: 共享内存消息传递

  • 共享内存
    共享内存的并发模型里, 线程之间共享程序的公共状态, 通过写-读内存中的公共状态来进行隐式通信;
  • 消息传递
    消息传递的并发模型里, 线程之间没有公共状态, 线程之间必须通过发送信息来显示进行通信;

共享内存并发模型中, 同步显示进行的;
消息传递并发模型中, 同步隐式进行的;
java的并发采用的是共享内存模型(因此需要显示进行同步)

2. java内存模型的抽象结构
java内存模型的抽象结构示意图
3. 数据依赖性

如果两个操作访问同一个变量, 且这两个操作中有一个为写操作, 此时这两个操作之间就存在数据依赖性

数据依赖类型

注意:

编译器和处理器在重排序时, 会遵守数据依赖性, 编译器和处理器不会改变存在数据依赖关系的两个操作的执行顺序;

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

4. 重排序

重排序 是指编译器处理器为了优化程序性能而对指令序列进行重新排序的一种手段;

重排序的类型

注意: 内存系统的重排序可能导致处理器对内存的写/读操作的执行顺序不一定与内存实际发生的读/写操作顺序一致

对于编译器的重排序, JMM的编译器重排序规则会禁止特定类型的编译器重排序;
对于处理器的重排序, JMM的处理器重排序规则会要求java编译器在生成指令序列时, 插入特定类型的内存屏障(Memory Barriers)指令, 通过内存屏障指令来禁止特定类型的处理器重排序

内存屏障的类型

JMM这样做的目的是: 为程序员提供一致的内存可见性的保证

5. 顺序一致性内存模型(理论模型 参考模型)

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

顺序一致性内存模型为程序员提供的视图

注意: 正确同步的多线程程序的执行结果与该程序在顺序一致性内存模型中的执行结果相同(即可以利用程序在顺序一致性内存模型的执行结果来判断自己编写的多线程程序是否符合期望)

6. volatile 的内存语义
  • volatile 的特性
    可见性: 对一个volatile变量的读, 总是能看到(任意线程)对这个volatile变量最后的写入;
    原子性: 对任意单个volatile变量的读/写具有原子性, 但类似于volatile++这种复合操作不具有原子性;

  • volatile 写-读的内存语义
    当写一个 volatile 变量时, JMM会把该线程对应的本地内存中所有的共享变量的值刷新到主内存;
    当读一个 volatile 变量时, JMM会把该线程对应的本地内存置为无效, 线程接下来从主内存中读取共享变量;

  • volatile 内存语义的实现

    JMM针对编译器指定的volatile重排序规则

    编译器的volatile重排序规则

    1. 当第二个操作是 volatile 时, 不管第一个操作是什么, 都不能重排序(这个规则确保 volatile 之前的操作不会被编译器重排序到 volatile写 之后)
    2. 当第一个操作是 volatile 时, 不管第二个操作是什么, 都不能重排序(这个规则确保 volatile 之后的操作不会被编译器重排序到 volatile 之前;
    3. 当第一个操作是 volatile, 第二个操作是 volatile 时, 不能重排序;

    JMM针对处理器指定的volatile重排序规则
    为了实现 volatile 的内存语义, JMM采取保守策略, 编译器在生成字节码时, 会在指令序列中插入内存屏障来禁止特定类型的处理器重排序;

    处理器的volatile重排序规则

    volatile写的指令序列示意图

    volatile读的指令序列示意图

7. 锁的内存语义
  • 锁的释放和获取的内存语义
    当线程释放锁时, JMM会把该线程对应的本地内存中的共享变量刷新到主内存中;
    当线程获取锁时, JMM会把线程对应的本地内存置为无效;

注意: 锁释放与volatile写有相同的内存语义; 锁获取与volatile读有相同的内存语义;

  • 锁释放-获取的内存语义的实现
    1. 利用 volatile 变量的写-读所具有的内存语义;
    2. 利用 CAS 所附带的 volatile 读 和 volatile 写的内存语义;
8. final 域的内存语义
  • final 域的重排序规则

    1. 在构造函数内对一个 final域 的写入, 与随后把这个被构造对象的引用赋值给一个引用变量, 这两个操作之间不能重排序;(
      final
      )
    2. 初次读一个包含 final 域 的对象的引用, 与随后初次读这个final域, 这两个操作之间不能重排序; (final)
  • final 域的重排序规则
    final 域的重排序规则禁止把 final 域的写重排序到构造函数之外, 实现该规则需要:

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

final 域的重排序规则可以保证在对象引用为任意线程可见之前, 对象的 final 域已经被正确初始化过了

  • final 域的重排序规则
    final 域的重排序规则禁止处理器重排序初次读一个包含 final 域对象的引用和初次读这个 final, 实现该规则需要:
    编译器在读 final 域操作的前面插入一个 LoadLoad 屏障

final 域的重排序规则可以保证在读一个对象的 final 域之前, 一定会先读包含这个 final 域的对象的引用

  • 写引用类型的 final 域的额外重排序规则
    对于引用类型, 写 final 域的重排序规则对编译器和处理器增加了如下约束:
    在构造函数内对一个 final 引用的对象的成员域的写入, 与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量, 这两个操作之间不能重排序

注意: final 的内存语义向程序员保证了只要对象是正确构造的(被构造对象的引用在构造函数中没有"逸出")则不需要使用同步就可以保证任意线程都能看到这个 final 域在构造函数中被初始化之后的值

9. happens-before 规则(JMM对程序员的承诺)

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;

5). start()规则
如果线程A执行操作ThreadB.start()(启动线程B), 那么A线程的ThreadB.start()操作 happens-before 于线程B中的任意操作;

6). join()规则
如果线程A执行操作ThreadB.join()并成功返回, 那么线程B中的任意操作 happens-before 于线程A从 ThreadB.join()操作成功返回(的后序操作);

7). 线程终结规则
线程中所有的操作都先行发生于线程的终止检测;

8). 对象终结规则
一个对象的初始化完成先行于它的finalize()方法的开始;

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

推荐阅读更多精彩内容