Java多线程开发(三)Java内存模型和同步机制

使用内存模型提供同步机制

由上一节的内容我们知道了Java提供了synchronizedvolatile、原子类等工具来帮助我们我们构建线程安全的程序。那么这一节我们就来探究这些工具的设计理念和实现方法。

之前我用现代计算机的多处理器架构来描述线程安全的出现原因。但实际情况还要比这更复杂些:不同的处理器架构实现多有不同,但Java程序是需要跨平台运行的,所以Java虚拟机就需要提供一个规范来作为应用程序和底层平台之间的桥梁,以此保证 当程序代码按照这个规范正确开发之后,无论在什么平台上执行都能得到正确的结果。这个规范就是Java内存模型(Java Memory Model,简称JMM)。

JMM对应用程序的保证

要想研究JMM是如何让程序正确的运行的,那么我们首先要明确这个“正确”到底该如何定义。
根据冯·诺伊曼模型的经典的串行计算模型,当程序只存在唯一的操作执行顺序,即按代码顺序依次执行,且在执行序列中每个指令的执行结果都对之后的指令可见。这种情况下程序当然是正确运行的,这又被称为 “串行一致性”。但是,这个模型存在着两个问题:

  1. 现代多核处理器会并行执行代码,无法保证串行。
  2. 严格的串行不利于处理器优化代码,提高运行速度。

于是JMM对于程序的执行结果的可预测性和易开发性等进行了权衡,规定了JVM必须遵守的一组保证,这些保证规定了对变量进行写入操作的时候在什么时候保证对其他线程可见,以及在什么时候保证操作的原子性。

原子性保证

JMM规定对long/double型以外的基本数据类型以及引用类型的共享变量进行读、写操作都具有原子性。另外,JMM还特别规定对volatile修饰的long/double型共享变量进行读、写操作也具有原子性。换而言之,对引用类型以及几乎所有基本数据类型的共享变量进行的读、写操作,Java内存模型都保证它们具有原子性。

之所以long/double类型比较特别,是因为JVM允许将64位的读操作或者写操作分解为两个32位的操作。这就导致对long/double进行读写时,可能前32位已经更新了,而后32位还未更新,导致得到一个错误值。

Happens-Before规则

在Java语言规范(The Java Language Specification)的17.4.5节这一节中,规范定义了程序执行时所有操作之间存在的三种顺序:Program Order(代码顺序)、Synchronization Order(同步顺序) 和 Happens-before Order(更先发生顺序)。

  • 代码顺序:最严格的顺序,就是程序按照前面所说的串行一致性,即 “正确” 的执行顺序来运行。
  • 同步顺序: 定义了synchronizes-with关系,对于拥有这种关系的两个操作A,B(A synchronizes-with B),A,B的执行顺序必须和代码顺序一致,即 A 一定要先于 B 执行。
    同步顺序包括以下规则:
    1. 同一个监视器锁(包括内置锁和显式锁)上的锁的释放 synchronizes-with 和后续所有对该锁的申请。
    2. 同一个volatile变量上的写操作 synchronizes-with 后续所有读操作。
    3. 用默认值(0,false 或者 null) 对每个变量初始化 synchronizes-with 每一个线程对这个变量的首次使用。
    4. 调用一个线程的start方法 synchronizes-with 被启动的线程中的第一个操作。
    5. 一个线程中的最后一个操作 synchronizes-with 任何(以 isAlive()或者join())监听到这个线程已经结束的线程。
    6. 一个线程调用另一个线程的interrupt()方法 synchronizes-with 被中断线程检测到 interrupt()调用
  • 更先发生顺序: 定义了happens-before关系,对于拥有这种关系的两个操作A,B(A happens-before B),A的执行结果一定要对B可见。
    更先发生顺序包括以下规则:
    1. 如果操作A、B在同一个线程执行,且在代码顺序中A在B之前,那么A happens-before B。
    2. 对象的构造函数的完成 happens-before 这个对象的析构函数(借用C++中的概念)的启动。
    3. 如果A synchronizes-with B,那么一定有A happens-before B
    4. happens-before关系拥有传递性:如果A happens-before B;B happens-before C,那么一定有A happens-before C。

可以看到,这三种规则是层层放宽的,而规范对所有平台要求的就是,执行所有代码必须遵守Happens-before Order:
原文如下:

When a program contains two conflicting accesses that are not ordered by a happens-before relationship, it is said to contain a data race. ……A program is correctly synchronized if and only if all sequentially consistent executions are free of data races.If a program is correctly synchronized, then all executions of the program will appear to be sequentially consistent.

规范将两个更先发生顺序规定的操作没有满足happens-before关系称之为数据争用,并且证明当程序运行时不存在数据争用时,这个程序就是正确执行的
同时需要注意的是,规范虽然通过更先发生顺序定义了何为正确的执行顺序,但是规范没有要求所有的平台必须按照这个顺序执行代码,而是只要代码执行结果跟正确的执行顺序的结果一致,那么平台可以用任意顺序执行(重排序)代码

It should be noted that the presence of a happens-before relationship between two actions does not necessarily imply that they have to take place in that order in an implementation. If the reordering produces results consistent with a legal execution, it is not illegal.

规范还举了个例子:同步顺序有一个规则 用默认值(0,false 或者 null) 对每个变量初始化 synchronizes-with 每一个线程对这个变量的首次使用。 但是实际上,处理器只需要在每个线程第一次使用到这个变量之前给这个变量用默认值赋值就可以了,因为这两种方式的实际效果是一样的。
我们实际上也经常遇到这方面的一个表现:

int a = 5;
int b = 6;
int c = a + b;

对于这段代码,虽然更先发生顺序要求 如果操作A、B在同一个线程执行,且在代码顺序中A在B之前,那么A happens-before B,所以处理器应该要先初始化a,再初始化b。但是实际上,处理器可能先初始化a,也可以先初始化b,因为这两种方式对程序结果没有影响。

Happens-Before规则解析

Happens-Before规则具有传递性,所以语句之间的可见性是可以累积的。接下来我们具体分析synchronizedvolatile的安全性是怎么产生的。

分析 synchronized

现在有两个线程A,B;它们执行操作如下:
程序流程
对于线程A,B来说,根据同步顺序第一条(简写为SO1)和更先发生顺序第三条(简写为HB3),有A2 happens-before B1(简写为A2 →B1);根据HB1,又有A1→A2、B1→B2,所以根据HB4,有: A1→A2→B1→B2,即:
A在释放锁前执行的所有操作的结果都对B在获取锁后的所有操作可见。 这样就实现了线程间操作的同步。由此我们也可以发现,happens-before 关系产生的可见性是可以延续到A2(锁的临界区)之前的操作A1,但是由于A1在临界区之外,所以虽然B1可以看到A1的执行结果,但却不能保证这个结果是最新的(因为其他线程能在A2之后B1修改A1执行的结果)。

分析 volatile

现在有两个线程A,B;它们执行操作如下:
程序流程
A2修改了一个volatile变量,而B1(在A2执行之后)读取了该变量,根据 SO2,有A2→B1,又根据HB1,又有A1→A2、B1→B2,所以根据HB4,同样有: A1→A2→B1→B2,所以volatile关键字能够对可见性和有序性进行保障,具体表现和上述锁的作用十分类似。

关于JMM关于Happens-Before规则就说到这儿了,我想吐槽的是,我通过书学习这一块的内容的时候,对HB1规则的作用想了很久都没有理解,认为这个跟重排序的表现冲突了。最后看规范才知道,原文说明了处理器不需要按这个规则执行,只需要让执行结果跟这个规则执行的结果一致就可以了。所以说,还是要看文档啊,而如果你看到这觉得我说的也有逻辑不通或者其他问题的话,也还是去看文档吧,文档地址我在前面已经给出来了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值