39
java.sun.com/javaone/sf | 2004 JavaOne SM Conference | Session TS-2331 1 最新 Java™ 技术内存 模型 Jeremy Manson William Pugh 马里兰大学 http://www.cs.umd.edu/~pugh

最新Java技术内存模型

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: 最新Java技术内存模型

java.sun.com/javaone/sf

| 2004 JavaOneSM Conference | Session TS-23311

最新 Java™ 技术内存模型

Jeremy Manson 和 William Pugh马里兰大学 http://www.cs.umd.edu/~pugh

Page 2: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23312

听众

• 假设你熟悉基于 Java™技术的线程(“ Java™ 线程”)的基础知识 ─ 创建、启动和加入线程─ 同步

─ wait和 notifyAll

Page 3: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23313

Java™ 线程规范

• 修订为 JSR-133 的一部分

• 属于以下规范中的一部分 ─ 最新 Java™ 语言规范

─ 最新虚拟机规范

• 今天在此讨论是 JDK™ 1.5 版本中的功能─ 不是所有这些功能都能保证在以前版本中运行─ 突破以前的线程规范

─禁止许多 Java™ 虚拟机执行优化

Page 4: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23314

多线程系统中的安全问题

• 不要保留许多直觉假设

• 某些广泛使用的习惯用法并不安全─ 最初的双重检查锁定用法

─ 检查用于线程结束的 non-volatile 标志

• 不能使用测试来检查错误─ 有些异常情况仅在某些平台上出现

─例如,多处理器

─ 异常情况很少并且不重复地出现

Page 5: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23315

修订线程规范

• Java™ 线程规范已历经重大修订─ 多数修订是为适当地规范现有行为─ 但行为方面有少数更改

• 目标─ 清晰并且易于理解─ 培植可靠的多线程代码

─ 可供高性能的 Java™ 虚拟机使用

• 已影响 Java™ 虚拟机─ 以及已编写出的较差的现有代码

─包括 Sun 的 JDK 部分

Page 6: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23316

本次演讲……

• 用 Java™ 语言描述构建同步阻塞和并发程序─ 语言原语和 util.concurrent 抽象

• 说明正确同步化代码的意义

• 尽力使你确信,关于不同步代码的智能推断几乎肯定可以是错误的─ 高效和可靠的程序不需要

Page 7: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23317

本次演讲……

• 我们将主要讨论─ 同步方法和阻塞

─ Volatile 域

• 同一原则适用于 JSR-166 锁定和原子性操作

• 还将讨论 final 域和不可变性

Page 8: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23318

分类学

• 高级并发抽象─ JSR-166 和 java.util.concurrent

• 低级锁定─ synchronized()阻塞

• 低级原语─ Volatile 变量,

java.util.concurrent.atomic 类

─ 考虑非阻塞同步

• 数据争用:故意不完全同步─ 避免!

─ 甚至 Doug Lea 也不能正确了解

Page 9: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-23319

同步的三个方面

• 原子性─ 锁定以获得互斥

• 可见性─ 确保一个线程中对象域的更改在其它线程中可

• 顺序性

─ 确保对语句执行的顺序不会感到惊奇

Page 10: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233110

不要试图太聪明

• 人们担心同步的成本

─ 尝试不使用同步,设计模式在线程之间进行通信

─锁定、 volatile或其它并发抽象

• 几乎不可能正确执行

─ 无同步的线程内通信是不直观的

Page 11: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233111

测验时间

x = y = 0

x = 1

j = y

线程 1

y = 1

i = x

线程 2

能得到 i = 0 和 j = 0 这样的结果么 ?

启动线程

Page 12: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233112

答案:是!

x = y = 0

x = 1

j = y

线程 1

y = 1

i = x

线程 2

如何得到 i=0 和 j=0?

启动线程

Page 13: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233113

这是如何发生的 ?

• 编译器可以对语句重新排序─ 或者保持寄存器中的值

• 处理器可以对其重新排序

• 在多处理器中,值不能在全局存储器中同步

• 内存模型的设计旨在允许强力优化─ 包括尚未实现的优化

• 对性能有益─ 对有关不充分的同步代码的直觉不利

Page 14: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233114

正确性和优化

• 根据你认为系统必须按照的顺序而设计的巧妙代码在 Java 语言中几乎总是错误的

• Dekker 的算法(第一个正确锁定实现)要求此顺序─ 在 Java 语言中不能运行,使用提供的锁定

• 必须使用同步来加强可见性和顺序性─ 以及互斥─ 如果你正确使用同步,那么你不会看到重新排序

Page 15: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233115

同步操作(近似)

// block until obtain locksynchronized(anObject) { // get main memory value of field1 and field2 int x = anObject.field1;int y = anObject.field2; anObject.field3 = x+y;// commit value of field3 to main memory}// release lockmoreCode();

Page 16: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233116

操作何时对其它线程可见 ?

glo = ref1

unlock M

线程 1

lock M

ref2 = glo

线程 2

lock M

ref1.x = 1

unlock M

j = ref2.x

解除锁定(释放)之前的一切

对同一对象的随后锁定(获取)之后的一切都可见

Page 17: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233117

释放和获取

• 释放之前的所有访问─ 提前排序,并可见 ─ 匹配获取之后的任意访问

• 解除锁定监视器 / 锁定是一种释放─ 由该监视器 / 锁定的下列任何锁定获取

Page 18: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233118

排序

• Roach motel排序─ 编译器 / 处理器可将访问移入同步阻塞

─ 仅在某些特殊情况下,能将其移出,通常不可见

• 某些特殊情况:─ 线程局部对象上的锁是空操作─ 可重入锁是空操作

Page 19: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233119

Volatile 域

• 如果某个域可同时被多个线程访问,并且访问中至少有一个是写─ 将域设置为 volatile

─记录─赋予必要的 Java™ 虚拟机保证

─ 要操作正确则很复杂,但没有 volatile 几乎是不可能的

• volatile做些什么 ?─ 直接向内存读和写

─不缓存或保留在寄存器中─ Volatile long 和 double 是原子型的

─对非 volatile long 和 double 不适用

─ 编译器重新排序 volatile访问受限制

Page 20: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233120

Volatile释放 / 获取

• volatile 写是一种释放─ 由随后读取同一变量获取

• 在 volatile 写之前的所有访问─ 在 volatile读取之后的所有访问之前排序,并且这些访问可见

Page 21: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233121

class Animator implements Runnable { private volatile boolean stop = false; public void stop() { stop = true; } public void run() { while (!stop) oneStep(); } private void oneStep() { /*...*/ }}

Volatile 保证了可见性

• stop必须声明为 volatile─ 否则,编译器可能将其保存在寄存器中

Page 22: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233122

class Future {private volatile boolean ready;private Object data;public Object get() {

if (!ready) return null;

return data;}

Volatile 保证了顺序

• 如果线程读取了数据,则 ready 上的释放 / 获取保证了可见性和顺序性

public synchronized void setOnce(Object o) {

if (ready) throw … ; data = o;ready = true;}

}

Page 23: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233123

其它获取和释放

• 其它操作构成了释放 / 获取对

• 启动线程是一种释放─ 由线程的 run 方法获取

• 结束线程是一种释放─ 由和结束线程一起的任一线程获取

Page 24: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233124

防止数据争用

• 攻击者可通过数据争用将对象实例传给其它线程

• 可造成不可思议的问题 ─ 可在某些 JVM 中观察到

─ 在旧式 JVM 中,可以看到 String 对象更改

─从 /tmp 更改为 /usr

• 如果类的安全性很重要,必须采取措施

• 选择:─ 使用同步(即使在构造器中)

─ 通过把所有域设置为 final ,使对象不可变

Page 25: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233125

不可变的类

• 将所有关键域设置为 final• 直到该对象已完全构造好,其它线程才能看到对象

• JVM会负责确保该对象被视为不可变─ 即使恶意代码使用数据争用攻击该类

Page 26: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233126

Final 域的优化

• 新规范允许强力优化 final 域─ 通过同步和未知方法调用提升读 final 域─ 仍保留不可变性

• 应该考虑到未来的 JVM 获得性能优势

Page 27: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233127

必要时进行同步

• 线程交互作用的位置─ 需要同步─ 可能需要慎重考虑─ 可能需要记录─ 必要的同步成本不重要

─适用于多数应用程序

─无须复杂化

Page 28: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233128

同步类

• 某些类被同步─ Vector、 Hashtable、 Stack─ 多数输入 /输出流

─ 不需要的同步开销可以度量

• 与集合类对比─ 默认情况下,不同步─ 可以请求同步版本

─ 或可使用 java.util.concurrent 版本 ( Queue、 ConcurrentMap实现)

• 使用同步类─ 通常不能满足并发交互需要

Page 29: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233129

同步的集合并不总能满足需要

• 事务(不使用)─ 破坏原子性……

ID getID(String name) {ID x = h.get(name);if (x == null) {

x = new ID();h.put(name, x);

}return x;

}• 迭代

─ 其它线程在集合中进行迭代运算时,不要修改此集合

Page 30: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233130

并发交互

• 通常需要整个事务是原子的─ 读取和更新映射─ 将记录写到输出流上

• 输出流被同步─ 可以有多个线程试图写入同一输出流─ 每个线程的输出不能确定为交叉存取─ 基本上无用

Page 31: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233131

util.concurrent

• java.util.concurrent 中的东西很不错,请使用它

• ConcurrentHashMap有一些附加功能可避开事务处理的问题─ putIfAbsent─ 并发迭代

• CopyOnWrite类允许并发迭代和无阻塞读─ 修改的成本太高,应尽量少用

Page 32: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233132

设计快速代码

• 做快之前先做对

• 减少同步成本─ 避免在线程间共享可变对象

─ 避免旧集合类( Vector、 Hashtable)

─ 使用块 I/O (或者,甚至更好, java.nio 类)

• 使用 java.util.concurrent 类─ 为速度、可扩展性和正确性而设计

• 避免锁争用─ 减少锁定范围

─ 减少锁定时间

Page 33: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233133

故障所在

• 考虑内存屏障─ 没有什么可提供内存屏障的影响

• 最初的双重检查用法─ AKA 多线程迟缓初始化

─ 任何非同步的非 volatile 型读 / 写引用

• 根据可见性的睡眠

• 关于数据争用的因果智能推断

Page 34: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233134

线程局部对象的同步

• 线程局部对象的同步─ (仅由单一线程访问的对象)─ 没有语义或含义─ 编译器可以删除─ 也可以删除可重入同步

─例如,从同一对象的其它同步方法中调用某个同步的方法

• 这是人们暂时讨论的一种优化─ 不确定现在还是否有人使用

Page 35: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233135

线程安全的迟缓初始化

• 要对许多线程共享的对象执行迟缓初始化

• 在初始化对象后,不要补偿同步

• 标准的双重检查锁定无法运行─ 使用检查的域 volatile 来修复

• 如果两个线程可能同时访问某个域,其中一个写入─ 域必须是 volatile

Page 36: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233136

结尾

• 同步操作的成本非常高─ 但必要的同步成本很少

• 线程交互需要慎重考虑─ 不要太聪明─ 没有必要将重新排序考虑的很难

─程序中无数据争用,

无可见的重新排序

• 需要内部线程通信……

Page 37: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233137

结尾—通信

• 线程间的通信─ 要求两个线程通过同步进行交互

• JSR-133 和 166提供了通信的新机制─ 高级并发性框架

─ Volatile 域

─ Final 域

Page 38: 最新Java技术内存模型

| 2004 JavaOneSM Conference | Session TS-233138

问与答

Page 39: 最新Java技术内存模型

java.sun.com/javaone/sf

| 2004 JavaOneSM Conference | Session TS-233139

最新 Java™ 技术内存模型

Jeremy Manson 和 William Pugh马里兰大学 http://www.cs.umd.edu/~pugh