JVM内存模型系列 (三)先行发生原则

大家好,我是编哥,JMM(JVM内存系列)今天继续。

在之前的两篇关于JMM的文章中,编哥带大家理解了Java内存模型的定义,以及这个模型中的8个基本操作,相信大家对于Java的内存模型已经有了大概的印象

我们来简单回顾一下,然后阐述 先行发生原则,它是判别 变量的读写,是否在并发下安全正确的天然法则,这将对我们理解线程并发,有着非常重要的作用

内存模型是规定变量读写的底层细节

cec8bac3728c2b6989a603c93d0a39e3.jpeg
内存模型及其操作

变量的存取,是程序必然发生的一件事,多个线程对同一个变量的读写,在不受某些规则约定时,势必导致冲突

在这里,编哥再次提醒各位,咱们现在讨论的变量,是成员变量,也就是那些属于类的变量,而不是那些写在方法里面的局部变量和方法参数哦,因为局部变量和方法参数是线程私有的。

再说清楚点,就算某个局部变量是对堆上公有对象的引用,那么我们要知道,这个引用着其他对象的reference本身,是方法在Java栈上运行时,方法局部变量表中的一员,它是不和其他线程共享的,因为其他线程有自己的Java栈空间

好了,为了让属于类的实例字段,静态字段,构成数组对象的元素,在并发访问时,避免线程之间争用它们,导致程序出错,严谨的内存模型应运而生

内存模型里的内存,可以分为两个地方

  • • 工作内存

  • • 主内存

这两个地方的数据传递,又由8个操作完成

组合使用8种操作,支持三种特性

8个操作我们在前文已经阐述了,通过对变量施加操作约束程度的不同,我们可以看到 volatile 变量其实就是捆绑了 read, load, use 3个操作在一起,保证每次都要从主内存读取变量的值,而非简单普通变量那样有可能直接 从工作内存读,而不发生 read 的操作

其实,如果我们抛开8个操作的具体细节,实际上可以通过一个称为 先行发生原则(happens-before) 的 判断方式,来检查变量是否会在并发的时候,存在数据上的读写竞争

我们知道,在并发的情形下,如果多个线程的执行是有序的,如同我们期盼的那样,而且变量的修改是对其他线程可见的,不存在延迟,不存在没来得及写回主内存的情况,最后,变量的访问写入能够原子性地完成,令其他线程不会看到一个初始化了一半的残缺对象,如果这三件事都圆满达到,那么多个线程在我们程序中就算玩命并发,还是安全的

这三个事关并发的特性,总结来说就是

  • • 有序性

  • • 可见性

  • • 原子性

比如,volatile 就保证了变量在并发情形下的可见性

再者,我们将方法用 synchronized 进行修饰,而这个方法中存在 读写一个堆上的对象变量 的语句,那么对此变量的并发访问的原子性就得以保证

先行发生原则

我们不需要深入理解JVM是如何依赖JMM规定的8种操作,来逐渐构建起来高效并发之基础的

在实际编码过程中,通过一些简单的规则,我们就可以判断并发环境下的两个操作是否存在冲突,而无需被Java内存模型的细节困扰, 如下:

  • • 1)程序次序规则:写在前面的代码就先执行,也就是说,变量的读写控制流,一如我们在方法里写的那样,尽管可能中间存在乱序执行,但是一定不会发生我们意料之外的怪事

例子:

int i = 1; // a
int j = 2; // b
int k = i; // c

使用一个线程来运行上述代码,那么cpu或许会先执行 b,再执行 a语句,但是c一定是在a后面,程序次序规则向你保证了这一点

  • • 2)管程锁定规则:unlock 必然先行发生于lock

class MyObj {
    void synchronized method1() {
        //...
    }
    void synchronized method2() {
        //...
    }
}

method2想加锁,必在 method1 解锁后,就是说,method1 释放了 MyObj 的锁(unlock),method2 才接着能对 MyObj 加锁(lock)

  • • 3)volatile变量规则:写 先行发生于 读

解释:你可以理解为 volatile 的写入,一如 上面管程锁定规则中的 unlock, 而读取,一如 lock

  • • 4)线程启动规则:Thread对象的 start() 方法,先行发生于 此方法的任何一个动作

  • • 5)线程终止规则:Thread对象的 所有动作都 先行发生于 对线程的终止检测

  • • 6)线程中断规则:interrupt() 先行发生于 线程检测到中断事件的发生

  • • 7)对象终结规则:对象的初始化完成,先行发生于 对象的 finalize()方法的开始(这不很显然吗?🐶)

  • • 8)传递性规则:如果A 先行发生于 B, B 先行发生于 C,那么 A 先行发生于 C

结语

Java内存模型至此我们就聊完了,在实际的编码过程中,我们或许用不到时刻分析变量的读取是否在内存模型层面做好了线程安全,只需要记住 先行发生原则 就足以享受严谨的Java内存模型给我们并发带来的安全性了。

更多时候,我们更在意如何正确使用java.util.concurrent包下的一些同步工具类,以及 synchronized 关键词,如果对你的Java技术提升有所帮助,欢迎点赞分享,我们接着就讲讲这一部分

JVM内存系列往期文章:

c038695e7c03f683b16d20ddbcf1ec37.jpeg

看这篇,再不怕 JVM 内存面试题:Java 内存模型系列(一)


65e206bed9c4ed233ea30adb71344115.jpeg

面试官:说说 JVM 里 volatile 变量的内存原理


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值