第三章 Java内存模型

 2019-12-10 16:15  阅读(1129)
文章分类:Java Core

第三章 Java内存模型

3.1Java内存模型的基础

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

线程之间的通信机制有两种:共享内存和消息传递

共享内存:线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式通信。

消息传递:线程之间没有公共状态,线程之间必须通过发送消息显示进行通信。

Java的并发是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。

3.1.2Java内存模型的抽象结构

JMM和主内存之间的抽象关系:

线程之间的共享变量存储在主内存汇总,每个线程都有自己的本地内存,本地内存中存储了该线程以读/写共享变量的副本。

JMM通过控制住内存与每个线程的本地内存之间的交互来为程序猿提供内存可见性保证。

3.1.3从源码到指令序列的重排序

重排序分为3种:

1.编译器优化的重排序:不改变单线程程序语义的前提下,可以重新安排语句的执行顺序

2。指令级并行的重排序:不存在数据依赖,那么可以改变语句对应的指令执行顺序

3.内存重排序:加载和存储操作看上去可能是在乱序中重排序。

20191210001686\_1.png

对于编译器,JMM的编译器重排序规则禁止特定类型的编译器重排序

对于处理器重排序,要求插入特定的内存屏障指令,通过内存屏障来紧致特定类型的处理器重排序。

3.1.4并发编程模型的分类

处理器重排序规则

20191210001686\_2.png

常见的处理器都允许Store-Load重排序:常见的处理器都不允许对存在数据操作做重排序。

为了保证内存可见性,Java编译器在省层指令序列的适当位置会插入内存屏障指令来紧致特定类型的处理器重排序。

JMM吧内存屏障指令分为4类

20191210001686\_3.png

3.1.5happens-before简介

这个规则用来阐述操作之间的内存可见性。

程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任一后续操作。

监视器锁规则:对于一个锁的解锁,happens-before于随后对这个锁的加锁。

volatile变量规则:对于一个voltile域的写,happens-before于任意后续对这个volatile域的读。

传递性:A happens-before B,且B happens-before C,那么A happens-before C

3.2重排序

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

3.2.1数据依赖

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

20191210001686\_4.png

3.2.2as-if-serrial语句

不管怎么排序,单线程程序执行的结果不会改变。

3.2.3程序顺序规则

再不改变happens-before规则情况下,JMM允许特定的重排序。

3.2.4重排序对多线程的影响

单线程中,对于控制依赖的操作重排序,不会改变执行结果,再多线程中,可能会改变程序的执行结果。

3.3顺序一致性

3.3.1数据竞争与顺序一致性

如果程序是正确同步的,程序的执行将具有顺序一致性-即程序执行记过与该程序在顺序一致性内存模型中的执行结果相同。

3.3.2顺序一致性内存模型

两大特性:

1.一个线程中的所有操作必须按照程序的顺序将执行。

2.所有程序都以能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每一个操作必须原子执行且立即对所有程序可见。

未同步程序在JMM中不但整体的执行顺序是无序的,而所有线程看到的操作执行顺序也啃呢个不一致。

3.3.3同步程序的一致性效果

锁同步和顺序一致性模型中的执行结果一致

3.3.4为同步程序的执行特征

1.顺序一致性模型波阿正单线程内的操作会按程序的顺序执行,而JMM不保证单线程内的操作按照程序的顺序执行。

2.顺序一致性模型保证所有线程只能看到一致的操作执行顺序,而JMM不能保证所有线程能看懂一致的操作执行顺序。

3.JMM不保证对64位long和double变来难过的写操作具有原子性,而顺序一致性模型保证对所有内存读写操作具有原子性。

3.4volatile的内存语义

3.4.1volatile的特性

volatile单个读写诗同一个锁对这些单个读写多同步。

volatile变量的特性:

可见性:对于一个volatile变量的读,总是能看到对这个volatile变量最后的写

原子性:对任意单个volatile变量的读/写具有原子操作,类似++这种复合操作,不具有原子性

3.4.2volatile写-读建立的happens-before关系

3.4.3volatile写-读的内存语义

当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到内存

当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效,线程将会从主内存中读取共享变量。

3.4.4volatile内存语义的实现

volatile重排序规则表

20191210001686\_5.png

可以看出

第一个操作为volatile读,不能进行重排序

第二个操作为volatile写,不能进行能重排序

第一个操作为volatile写,第二个操作为读,不能进行重排序

3.4.5JSR-133为什么要增强volatile的内存语义

严格限制编译器和处理器起对volatile变量与普通变量的重排序,确保volatile的写-读和锁的释放-获取具有相同的内存语义。

3.5锁的内存语义

3.5.1锁的释放与获取建立的happens-before关系

1.根据程序次序规则

2.根据监视器锁规则

3.根据传递性

3.5.2锁的释放和获取的内存语义

释放锁,JMM会将线程对应的共享内存变量刷新到主内存。

获取锁,JMM将线程的本地内存内存置为无效。线程必须从主内存后获取共享变量。

3.5.3锁内存语义的实现

公平锁和非公平锁的内存语义总结:

1.公平锁和非公平锁释放时,最后都要写一个volatile变量state

2.公平锁获取时,首先会读volatile变量

3非公平锁获取时,首先CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义。

锁释放-获取的内存语义的实现由下面两种:

1.使用volatile变量的写-读具有的内存语义

2.利用CAS所附带的volatile读和volatile写的内存语义。

3.5.4concurrent包的实现

Java线程之间通信的四种方式:

1.A线程写volaztile变量,随后B线程读这个volatile变量。

2.A线程写volatile变量,随后B线程用CAS更新这个volatile变量。

3.A线程用CAS更新一个volatile变量,随后B线程用CAS更新volatile变量

4.A线程利用CAS更新一个volatile变量,随后B线程读这个变量。

concurrent包的源码实现

首先,声明共享变量为volatile

然手使用CAS的原子操作更新来实现线程之间的同步

同时,配合volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

3.6final域的内存语义

3.6.1final域的重排序

final变量的两个重排序规则:

1.在构造器内对一个final域的写入,与随后把这个被构造器对象的引用赋值给一个引用变量,这俩个操作之间不能重排序。

2.初次读一个包含final域的对象引用,与随后初次读这个final域,这两个操作之间不能重排序。

3.6.2写final域的重排序

1.JMM禁止编译器把final域的写重排序到构造函数之外

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

3.6.3读final域的重排序规则

读final域的重排序规则是,在一个线程中,初次读该对象包含的final域,JMM禁止处理器重排序怼这两个操作。

3.6.4final域为引用类型

在构造函数内对一个final引用的对象的成员的写入,与随后在构造器外把这个被构造对象的引用复制给一个引用变量,这两个操作之间不能重排序。

3.6.5为什么final引用不能构造函数内“溢出”

在构造器返回之前,被构造对象的引用不能为其他线程所见。

3.6.6final语义在处理器中的实现

写final域的重排序规则会要求编译器在final域的写以后,构造器之前插入一个storestore屏障。读final域的重排序规则要求编译器在读final域之前出入loadload屏障。

3.6.7JSR-133为什么要增强final的语义

只要对象时正确构造的,那么不需要使用同步就可以保证任意线程都能看到这个final域在构造器中被初始化之后的值。

3.7happens-before

3.7.1JMM的设计

JMM向程序猿提供的happens-before规则能满足程序猿的需求

JMM对编译器和处理的束缚已经尽可能少。

3.7.2happens-before的定义

1.如果一个操作happend-before另一个擦偶走,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

2.两个操作存在happens-before关系,并不意味着java平台的具体实现必须要按照这个关系顺序执行。如果重排序执行的结果,与按照这个关系的结果一致,那么这种重排序并不非法。

3.7.3happens-before规则

start()规则:如果线程A操作ThreadB.start(),那么线程A的ThreadB。start() 操作happend-start()于线程B中的任意操作。

3.8双重可检查锁定于延迟初始化

双重检查锁定时常见的延迟初始化,但是它是一个错误的用法。

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 第三章 Java内存模型

相关推荐