第16章 Java内存模型

终于看到这本书的最后一章了,嘿嘿,以后把这本书的英文版再翻翻。这本书中尽可能回避了java内存模型(JMM)的底层细节,而将重点放在一些高层设计问题,例如安全发布,同步策略等。它们的安全性都来自于JMM。本章将介绍Java内存模型的底层需求以及所提供的保证。

16.1 什么是内存模型,为什么需要它

Java高并发编程指南pdf java并发编程实战mobi_重排序

Java高并发编程指南pdf java并发编程实战mobi_Java高并发编程指南pdf_02

16.1.1 平台的内存模型

在共享内存的多处理体系架构中,每个处理器都拥有自己的缓存,并且定期地与住内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性。要想确保每个处理器在任意时刻都能知道其他处理器正在进行的工作,将需要非常大的开销。在大多数时间里,这种信息是不必要的,因此处理器会适当放宽存储一致性保证,以换取性能的提升。在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏或栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证。为了使java开发人员无需关心不同架构上内存模型之间的差异,Java还提供了自己的内存模型,并且JVM通过在适当位置上插入内存栅栏来屏蔽JMM与底层平台内存模型之间的差异

16.1.2 重排序

JMM可以使不同线程看到的操作执行顺序是不同的,从而导致在缺乏同步的情况下,要推断操作的执行顺序将变得更加复杂。各种操作延迟或者看似乱序执行的不容原因,都可以归为重排序。

Java高并发编程指南pdf java并发编程实战mobi_初始化_03

Java高并发编程指南pdf java并发编程实战mobi_内存模型_04

同步将限制编译器,运行时和硬件对内存操作重排序的方式,从而在实施重排序时不会破坏JMM提供的可见性保证。

16.1.3 Java内存模型简介

JMM为程序中所有的操作提供了一个偏序关系,称之为Happens-before。要想保证执行操作B的线程看到操作A的结果,那么A和B之间必须满足Happens-before关系。如果两个操作之间缺乏Happens-Before关系,那么JVM可以对它们任意地重排序。

Java高并发编程指南pdf java并发编程实战mobi_内存模型_05

Java高并发编程指南pdf java并发编程实战mobi_内存模型_06

16.1.4 借助同步

由于Happens-Before的排序功能很强大,因此有时候可以"借助"(Piggyback)现有同步机制的可见性属性。这需要将Happens-Before的程序规则与其他某个顺序规则结合起来,从而对某个未被锁保护的变量的访问操作进行排序。这项技术对语句的顺序非常敏感,因此很容易出错。它是一项高级技术,并且只有当需要最大限度提升某些类(例如ReentrantLock)的性能时,才应该使用这项技术。

在FutureTask的保护方法AbstractQueuedSynchronizer中说明了如何使用这种“借助”技术。

Java高并发编程指南pdf java并发编程实战mobi_初始化_07

 

Java高并发编程指南pdf java并发编程实战mobi_Java高并发编程指南pdf_08

Java高并发编程指南pdf java并发编程实战mobi_内存模型_09

Java高并发编程指南pdf java并发编程实战mobi_Java高并发编程指南pdf_10

 

16.2 发布

第3章介绍了如何安全地发布或者不正确地发布一个对象。其中介绍的各种安全技术,它们的安全性都来自于JMM提供的保证,而造成不正确发布的真正原因,就是在“发布一个共享对象”与“另一个线程访问该对象”之间缺少一种Happens-Before排序。

16.2.1 不安全的发布

如果无法确保发布共享引用的操作在另一个线程加载该共享引用之前执行,那么对新对象引用的写入操作将与对象中各个域的写入操作重排序。在这种情况下,另一个线程可能看到对象引用的最新值,但同时也将看到对象的某些或全部状态中包含的是无效值,即一个被部分构造的对象。错误的延迟初始化将导致不正确的发布,如程序16-3所示:

Java高并发编程指南pdf java并发编程实战mobi_内存模型_11

16.2.2 安全地发布

第3章介绍的安全发布常用模式可以确保被发布对象对于其他线程是可见的,因为它们保证发布对象的操作将在使用对象的线程开始使用该对象的引用之前执行。

16.2.3 安全初始化模式

有时候需要推迟一些高开销的对象初始化操作,并且只有当使用这些对象时才进行初始化。在程序清单16-4中通过将getResource方法声明为synchronized可以修复UnsafeLazyInitialization中的问题。由于getInstance的代码路径很短,因此如果geiInstance没有被多个线程频繁调用,那么在SafeLazyInitialization上不会存在激烈的竞争。

Java高并发编程指南pdf java并发编程实战mobi_重排序_12

Java高并发编程指南pdf java并发编程实战mobi_初始化_13

如程序16-5所示,通过提前初始化,避免了在每次调用SafeLazyInitialization中的getInstance时所产生的同步开销。在程序16-6的“延迟初始化占位类模式”中使用了一个专门的类来初始化Resource。JVM将推迟ResourceHolder的初始化操作,直到开始使用这个类时才初始化,并且由于通过一个静态初始化来初始化Resource,因此不需要额外的同步。

Java高并发编程指南pdf java并发编程实战mobi_重排序_14

Java高并发编程指南pdf java并发编程实战mobi_初始化_15

16.2.4 双重检查锁

DCL,已经被广泛的废弃了,pass

16.3 初始化过程中的安全性


小结:

Java高并发编程指南pdf java并发编程实战mobi_Java高并发编程指南pdf_16


终于把这本书看得差不多了....,呼呼呼,真不容易,总算又完结了一件事。这本书大概现在也只搞懂了一半左右,毕竟只看了一遍,糟糕的翻译质量为本书的阅读添加了不少的难度。除此之外,这本书理论大于实践,代码实践部分并不多,书中有很多前提知识是假定你已经知道的,所以适合有一定基础的人看。以后有时间把英文版拿来看看,最关键的是要在实践中运用这些知识才行。