JVM系列(4)——内存模型

本文详细探讨了Java内存模型,包括经典用例、官方描述、程序顺序、同步顺序、happens-before关系以及因果关系。强调了内存模型对多线程执行顺序的约束,以及如何确保线程间的数据可见性。通过实例分析了正确同步的重要性,指出过于严格的顺序一致模型和过于宽松的happens-before模型的局限性,最终阐述了Java内存模型是如何结合两者来平衡性能和正确性的。
摘要由CSDN通过智能技术生成

4 内存模型

主要参考:

  • 《JSR-133:Java内存模型与线程规范》;
  • 《Java Language Specification 16》的 17.4 章节 Memory Model。

首先澄清运行时数据区内存模型,这俩是完全不同的东西:

  • 运行时数据区:规定了 JVM 在使用内存时,应该将其分为几个部分,每个部分分别存储什么数据,有什么特性;
  • 内存模型:规定了在多线程场景下,程序以怎样的顺序执行是合法的。

4.1 经典用例

例一:

//共享变量
x = 0, y = 0;
//线程1执行
int r1 = x;
y = 1;
//线程2执行
int r2 = y;
x = 2;

如果编译器或 JVM 把 x = 2 或 y = 1 放到前面去执行,就可能观察到:r1 == 2,r2 == 1,这是否合法?

例二:

//共享变量
p = q;
p.x = 0;
//线程1
r1 = p;
r2 = r1.x;
r3 = q;
r4 = r3.x;
r5 = r1.x;
//线程2
r6 = p;
r6.x = 3;

编译器发现 r2 = r1.x,r5 = r1.x,于是直接把 r5 = r1.x 替换成 r5 = r2,这样效率更高,因为不用去内存里找某个对象的 x 变量,直接使用本地变量 r2。

但问题是,这样会观察到奇怪的结果:r2 == r5 == 0,r4 == 3,这是否合法?

这个问题不忙回答,是否合法不是随便说说的,需要有一个规范,这就是内存模型。

4.2 内存模型的官方描述

A memory model describes, given a program and an execution trace of that program, whether the execution trace is a legal execution of the program.

We do not need to concern ourselves with intra-thread actions (e.g., adding two local variables and storing the result in a third local variable). As previously mentioned, all threads need to obey the correct intra-thread semantics for Java programs.

Local variables (§14.4), formal method parameters (§8.4.1), and exception handler parameters (§14.20) are never shared between threads and are unaffected by the memory model.

可见,与 memory model (内存模型)并列的概念是 intra-thread semantics(线程内语义)。

当然,也可以理解为,memory model 在逻辑上包含了 intra-thread semantics,只是由于 intra-thread semantics 已经对线程内的程序顺序做了足够的约束,所以 memory model 对这部分不需要过分关注。

Java 程序的执行必须满足两个规范:

  • 线程内语义:决定线程孤立地执行过程;
  • 内存模型:决定线程从堆中取值时,能看到什么值。

这两个规范限制的不仅仅是 JVM(尤其是 JIT),同样也限制编译器。也就是说,如果编译器(特指前期编译,如 javac)或者 JIT 对程序进行了指令重排或者优化,并且不符合以上两个规范,那就是不合法的。

4.3 program order

Among all the inter-thread actions performed by each thread t, the program order of t is a total order that reflects the order in which these actions would be performed according to the intra-thread semantics of t.

简单理解,程序顺序就是指符合线程内语义的顺序。

4.3.1 一些概念

顺序一致性

  • 所有动作(比如读、写、加锁等)的执行顺序与程序顺序一致;
  • 每个动作都是原子的;
  • 每个动作执行后立刻对所有线程可见。

访问冲突(Conflicting Accesses):对同一个共享内存存在多个访问,并且至少有一个是写操作。

数据争用(Data Race):存在访问冲突,并且没有通过正确的同步方式(比如加锁、volatile 等)对它们的执行顺序进行排序。

关于顺序一致、数据争用与同步:

  • 一个程序,当且仅当所有顺序一致的执行过程都不存在数据争用,这个程序是正确同步的;
  • 如果一个程序是正确同步的,那么它唯一允许的行为是顺序一致的行为。
4.3.2 几个例子

例一:

//假设有共享变量:
public static int x = 1;
public static int y = 2;

//线程一执行:
public void thread1() {
   
    x = 0;
}

//线程二执行:
public void thread2() {
   
    y = 3;
}

这里不存在访问冲突,因为两个线程访问的是不同的变量。没有访问冲突,就更谈不上数据争用,自然也不需要同步。

例二

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值