Java内存模型——对应Java并发编程的艺术第三章

2019-08-28  本文已影响0人  于无声处写写写

内存模型基础

线程之间的通信机制有两种:共享内存、消息传递
JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证

三种重排序

常见的处理器都允许Store-Load重排序,但是都不允许对存在数据依赖的操作重排序。

happens-before

如果一个操作结果需要对另一个操作可见,那么这两个操作之间必须要存在happenes-before关系

happens-before规则:
程序顺序规则:一个县城中的每个操作,happens-before于该线程中的任意后续操作
监视器锁规则:解锁happens-before于加锁
volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
传递性:A-b b-c a-c

两个操作之间具有happenes-before关系,并不意味着前一个操作要在后一个操作之前执行,仅仅要求前一个操作对后一个操作可见,且前一个操作按顺序排在第二个操作之前。

重排序

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

数据依赖性:如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作就存在数据依赖性
因此,如果两个操作中有一个为写操作,如果这时候把这两个操作进行了重排序,那么就有可能会导致程序执行结果的变化

as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变

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

if(flag) ///操作3
{
  int i=a*a;  ///操作4
}

上面程序操作3和4就是存在控制依赖的两个操作,因此不能随意进行重排序

顺序一致性模型

两大特性:

JMM在具体实现上的基本方针为:在不改变程序执行结果的前提下,尽可能为编译器核处理器的优化大开方便之门。

volatile的内存语义

voaltile变量自身的特性:

volatile写和锁的释放有相同的内存语义
volatile读和锁的获取有相同的内存语义

volatile写的内存语义:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存
volatile读的内存语义:JMM会把该线程对应的本地内存置为无效,线程接下来将从主内存中读取共享变量
当第二个操作是volatile写时,不管第一个操作是什么,都不能重排序
当第一个操作是volatile读时,不管第二个操作是什么,都不能重排序。

JMM选择在每个volatile写的后面插入一个StoreLoad屏障
JMM实现的特点:首先确保正确性,然后再去追求执行效率。

锁的内存语义

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

锁的释放-获取的内存语义的实现至少有下面两种方式:

在concurrent包中的源代码会发现一个通用化的实现模式

final域的内存语义

写final域的重排序规则:禁止把final域的写重排序到构造函数之外
该规则可以确保在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了
读final域的重排序规则:初次读对象引用与初次读该对象包含的final域,JMM禁止处理器重排序这两个操作
该规则可以确保在读一个对象的final域之前,一定会先读包含这个final域对象的引用

构造函数返回前,被构造对象的引用不能为其他线程所见,因为此时的final域可能还没有被初始化。

happens-before

JMM把happens-before要求禁止的重排序分成了下面两类

双重检查锁定与延迟初始化

在首次发生下列任意一种情况时,一个类或者接口T将立即被初始化

上一篇 下一篇

猜你喜欢

热点阅读