• 1. 第9章 Java内存模型 2008年度“教育部-IBM精品课程”建设项目 IBM公司经费支持 重庆大学计算机学院建设 2009年3月
  • 2. 内容内存模型与可见性2初始化安全性4什么是内存模型31发生前关系(Happen-before)33IBM&重庆大学
  • 3. 9.1 Java内存模型同步和线程安全的许多底层混淆是 Java 内存模型 (JMM)的一些难以直觉到的细微差别。 并不是所有的多处理器系统都表现出缓存一致性 假如有一个处理器有一个更新了的变量值位于其缓存中,但还没有被存入主存,这样别的处理器就可能会看不到这个更新的值。 在缓存缺乏一致性的情况下,两个不同的处理器可以看到在内存中同一位置处有两种不同的值 内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节。 IBM&重庆大学
  • 4. 一个例子编译器为了优化一个循环索引变量,可能会选择把它存储到一个寄存器中,或者缓存会延迟到一个更适合的时间,才把一个新的变量值存入主存。 这些优化是为了帮助实现更高的性能,通常这对于用户来说是透明的,但是对多处理系统来说,这些复杂的事情可能有时会完全显现出来 JMM允许编译器和缓存对数据在处理器特定的缓存(或寄存器)和主存之间移动的次序拥有重要的特权 在缺乏同步的情况下,从不同的线程角度来看,内存的操作是以不同的次序发生的。 IBM&重庆大学
  • 5. “重新排序” (Reorder)描述几种对内存操作的真实明显的重新排序 当编译器不会改变程序的语义时,作为一种优化它可以随意地重新排序某些指令 在某些情况下,可以允许处理器以颠倒的次序执行一些操作 通常允许缓存以与程序写入变量时所不相同的次序把变量存入主存 会引发一些操作以不同于程序指定的次序发生 IBM&重庆大学
  • 6. 9.2 内存模型与可见性同步与可见性 不可变对象的问题 Volatile与可见性 IBM&重庆大学
  • 7. 同步与可见性 synchronized 关键字强制实施一个互斥锁 它强制实施某些内存可见性规则 当存在一个同步块时缓存被更新,当输入一个同步块时缓存失效 一个线程所写入的值对于其余所有的执行由同一监控器所保护的同步块的线程来说是可见的 确保了编译器不会把指令从一个同步块的内部移到外部 IBM&重庆大学
  • 8. 不可变对象的问题 不可变对象似乎可以改变它们的值 让一个对象的所有字段都为 final 并不一定使得这个对象不可变 所有的字段还必须是原语类型或是对不可变对象的引用 在将内存写方面的更改从一个线程传播到另一个线程时存在潜在的延迟,所以有可能存在一种竞态条件,即允许一个线程首先看到不可变对象的一个值,一段时间之后看到的是一个不同的值。 IBM&重庆大学
  • 9. Volatile与可见性 如何知道当线程 A 执行 someVariable=3 时,其他线程是否可以看到线程 A 所写的值 3? 可能是 someVariable 缓存在寄存器中,或者它的值写到写处理器的缓存中、但是还没有刷新到主存中 内存模型决定什么时候一个线程可以可靠地“看到”由其他线程对变量的写入 在多个线程访问同一个变量时,可以使用volatile 变量。 volatile 字段的读写直接在主存而不是寄存器或者本地处理器缓存中进行,并且代表线程对 volatile 变量进行的这些操作是按线程要求的顺序进行的。 IBM&重庆大学
  • 10. 9.3 发生前关系像对变量的读写这样的操作,在线程中是根据所谓的“程序顺序” 程序的语义声明它们应当发生的顺序——排序的 如果启动两个线程并且它们对任何公共监视器都不用同步执行、或者不涉及任何公共 volatile 变量 则完全无法准确地预言一个线程中的操作(或者对第三个线程可见)相对于另一个线程中操作的顺序。 IBM&重庆大学
  • 11. 顺序保证 JMM 描述了程序使用同步或者 volatile 变量以协调多个线程中的活动时所进行的顺序保证。 一个线程中的每个操作“发生之前”于这个线程程序规定的其他后续出现的操作 对监视器的解锁“发生之前”于同一监视器上的所有后续锁定 对volatile变量的写“发生之前”于同一 volatile变量 的每一个后续读 一个线程的Thread.start()调用“发生之前”于这个启动后的线程的其他操作 线程中的所有操作“发生之前” 从这个线程的 Thread.join() 成功返回的所有其他线程。IBM&重庆大学
  • 12. 9.4 初始化安全性JMM 还寻求提供一种新的初始化安全性保证 只要对象是正确构造的(意即不会在构造函数完成之前发布对这个对象的引用),然后所有线程都会看到在构造函数中设置的 final 字段的值 可以通过正确构造的对象的 final 字段可及的变量 ,也保证对其他线程是可见的 可以不用同步安全地访问这个 final 字段,编译器可以假定 final 字段将不会改变,因而可以优化多次提取。 IBM&重庆大学
  • 13. 构造函数中的final变量构造函数的 final 字段的写与在另一个线程中对这个对象的共享引用的初次装载之间有一个类似于 happens-before 的关系。 当构造函数完成任务时,对 final 字段的所有写(以及通过这些 final 字段间接可及的变量)变为“冻结”,所有在冻结之后获得对这个对象的引用的线程都会保证看到所有冻结字段的冻结值。 初始化 final 字段的写将不会与构造函数关联的冻结后面的操作一起重新排序 IBM&重庆大学
  • 14. 思考题分析下述程序的安全性 public class UnsafeLazyInitialization { private static Resource resource; public static Resource getInstance() { if (resource == null) resource = new Resource(); return resource; } }