Upload
yiditushe
View
1.862
Download
5
Embed Size (px)
DESCRIPTION
Citation preview
java.sun.com/javaone/sf
| 2004 JavaOneSM Conference | Session TS-23311
最新 Java™ 技术内存模型
Jeremy Manson 和 William Pugh马里兰大学 http://www.cs.umd.edu/~pugh
| 2004 JavaOneSM Conference | Session TS-23312
听众
• 假设你熟悉基于 Java™技术的线程(“ Java™ 线程”)的基础知识 ─ 创建、启动和加入线程─ 同步
─ wait和 notifyAll
| 2004 JavaOneSM Conference | Session TS-23313
Java™ 线程规范
• 修订为 JSR-133 的一部分
• 属于以下规范中的一部分 ─ 最新 Java™ 语言规范
─ 最新虚拟机规范
• 今天在此讨论是 JDK™ 1.5 版本中的功能─ 不是所有这些功能都能保证在以前版本中运行─ 突破以前的线程规范
─禁止许多 Java™ 虚拟机执行优化
| 2004 JavaOneSM Conference | Session TS-23314
多线程系统中的安全问题
• 不要保留许多直觉假设
• 某些广泛使用的习惯用法并不安全─ 最初的双重检查锁定用法
─ 检查用于线程结束的 non-volatile 标志
• 不能使用测试来检查错误─ 有些异常情况仅在某些平台上出现
─例如,多处理器
─ 异常情况很少并且不重复地出现
| 2004 JavaOneSM Conference | Session TS-23315
修订线程规范
• Java™ 线程规范已历经重大修订─ 多数修订是为适当地规范现有行为─ 但行为方面有少数更改
• 目标─ 清晰并且易于理解─ 培植可靠的多线程代码
─ 可供高性能的 Java™ 虚拟机使用
• 已影响 Java™ 虚拟机─ 以及已编写出的较差的现有代码
─包括 Sun 的 JDK 部分
| 2004 JavaOneSM Conference | Session TS-23316
本次演讲……
• 用 Java™ 语言描述构建同步阻塞和并发程序─ 语言原语和 util.concurrent 抽象
• 说明正确同步化代码的意义
• 尽力使你确信,关于不同步代码的智能推断几乎肯定可以是错误的─ 高效和可靠的程序不需要
| 2004 JavaOneSM Conference | Session TS-23317
本次演讲……
• 我们将主要讨论─ 同步方法和阻塞
─ Volatile 域
• 同一原则适用于 JSR-166 锁定和原子性操作
• 还将讨论 final 域和不可变性
| 2004 JavaOneSM Conference | Session TS-23318
分类学
• 高级并发抽象─ JSR-166 和 java.util.concurrent
• 低级锁定─ synchronized()阻塞
• 低级原语─ Volatile 变量,
java.util.concurrent.atomic 类
─ 考虑非阻塞同步
• 数据争用:故意不完全同步─ 避免!
─ 甚至 Doug Lea 也不能正确了解
| 2004 JavaOneSM Conference | Session TS-23319
同步的三个方面
• 原子性─ 锁定以获得互斥
• 可见性─ 确保一个线程中对象域的更改在其它线程中可
见
• 顺序性
─ 确保对语句执行的顺序不会感到惊奇
| 2004 JavaOneSM Conference | Session TS-233110
不要试图太聪明
• 人们担心同步的成本
─ 尝试不使用同步,设计模式在线程之间进行通信
─锁定、 volatile或其它并发抽象
• 几乎不可能正确执行
─ 无同步的线程内通信是不直观的
| 2004 JavaOneSM Conference | Session TS-233111
测验时间
x = y = 0
x = 1
j = y
线程 1
y = 1
i = x
线程 2
能得到 i = 0 和 j = 0 这样的结果么 ?
启动线程
| 2004 JavaOneSM Conference | Session TS-233112
答案:是!
x = y = 0
x = 1
j = y
线程 1
y = 1
i = x
线程 2
如何得到 i=0 和 j=0?
启动线程
| 2004 JavaOneSM Conference | Session TS-233113
这是如何发生的 ?
• 编译器可以对语句重新排序─ 或者保持寄存器中的值
• 处理器可以对其重新排序
• 在多处理器中,值不能在全局存储器中同步
• 内存模型的设计旨在允许强力优化─ 包括尚未实现的优化
• 对性能有益─ 对有关不充分的同步代码的直觉不利
| 2004 JavaOneSM Conference | Session TS-233114
正确性和优化
• 根据你认为系统必须按照的顺序而设计的巧妙代码在 Java 语言中几乎总是错误的
• Dekker 的算法(第一个正确锁定实现)要求此顺序─ 在 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();
| 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
解除锁定(释放)之前的一切
对同一对象的随后锁定(获取)之后的一切都可见
| 2004 JavaOneSM Conference | Session TS-233117
释放和获取
• 释放之前的所有访问─ 提前排序,并可见 ─ 匹配获取之后的任意访问
• 解除锁定监视器 / 锁定是一种释放─ 由该监视器 / 锁定的下列任何锁定获取
| 2004 JavaOneSM Conference | Session TS-233118
排序
• Roach motel排序─ 编译器 / 处理器可将访问移入同步阻塞
─ 仅在某些特殊情况下,能将其移出,通常不可见
• 某些特殊情况:─ 线程局部对象上的锁是空操作─ 可重入锁是空操作
| 2004 JavaOneSM Conference | Session TS-233119
Volatile 域
• 如果某个域可同时被多个线程访问,并且访问中至少有一个是写─ 将域设置为 volatile
─记录─赋予必要的 Java™ 虚拟机保证
─ 要操作正确则很复杂,但没有 volatile 几乎是不可能的
• volatile做些什么 ?─ 直接向内存读和写
─不缓存或保留在寄存器中─ Volatile long 和 double 是原子型的
─对非 volatile long 和 double 不适用
─ 编译器重新排序 volatile访问受限制
| 2004 JavaOneSM Conference | Session TS-233120
Volatile释放 / 获取
• volatile 写是一种释放─ 由随后读取同一变量获取
• 在 volatile 写之前的所有访问─ 在 volatile读取之后的所有访问之前排序,并且这些访问可见
| 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─ 否则,编译器可能将其保存在寄存器中
| 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;}
}
| 2004 JavaOneSM Conference | Session TS-233123
其它获取和释放
• 其它操作构成了释放 / 获取对
• 启动线程是一种释放─ 由线程的 run 方法获取
• 结束线程是一种释放─ 由和结束线程一起的任一线程获取
| 2004 JavaOneSM Conference | Session TS-233124
防止数据争用
• 攻击者可通过数据争用将对象实例传给其它线程
• 可造成不可思议的问题 ─ 可在某些 JVM 中观察到
─ 在旧式 JVM 中,可以看到 String 对象更改
─从 /tmp 更改为 /usr
• 如果类的安全性很重要,必须采取措施
• 选择:─ 使用同步(即使在构造器中)
─ 通过把所有域设置为 final ,使对象不可变
| 2004 JavaOneSM Conference | Session TS-233125
不可变的类
• 将所有关键域设置为 final• 直到该对象已完全构造好,其它线程才能看到对象
• JVM会负责确保该对象被视为不可变─ 即使恶意代码使用数据争用攻击该类
| 2004 JavaOneSM Conference | Session TS-233126
Final 域的优化
• 新规范允许强力优化 final 域─ 通过同步和未知方法调用提升读 final 域─ 仍保留不可变性
• 应该考虑到未来的 JVM 获得性能优势
| 2004 JavaOneSM Conference | Session TS-233127
必要时进行同步
• 线程交互作用的位置─ 需要同步─ 可能需要慎重考虑─ 可能需要记录─ 必要的同步成本不重要
─适用于多数应用程序
─无须复杂化
| 2004 JavaOneSM Conference | Session TS-233128
同步类
• 某些类被同步─ Vector、 Hashtable、 Stack─ 多数输入 /输出流
─ 不需要的同步开销可以度量
• 与集合类对比─ 默认情况下,不同步─ 可以请求同步版本
─ 或可使用 java.util.concurrent 版本 ( Queue、 ConcurrentMap实现)
• 使用同步类─ 通常不能满足并发交互需要
| 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;
}• 迭代
─ 其它线程在集合中进行迭代运算时,不要修改此集合
| 2004 JavaOneSM Conference | Session TS-233130
并发交互
• 通常需要整个事务是原子的─ 读取和更新映射─ 将记录写到输出流上
• 输出流被同步─ 可以有多个线程试图写入同一输出流─ 每个线程的输出不能确定为交叉存取─ 基本上无用
| 2004 JavaOneSM Conference | Session TS-233131
util.concurrent
• java.util.concurrent 中的东西很不错,请使用它
• ConcurrentHashMap有一些附加功能可避开事务处理的问题─ putIfAbsent─ 并发迭代
• CopyOnWrite类允许并发迭代和无阻塞读─ 修改的成本太高,应尽量少用
| 2004 JavaOneSM Conference | Session TS-233132
设计快速代码
• 做快之前先做对
• 减少同步成本─ 避免在线程间共享可变对象
─ 避免旧集合类( Vector、 Hashtable)
─ 使用块 I/O (或者,甚至更好, java.nio 类)
• 使用 java.util.concurrent 类─ 为速度、可扩展性和正确性而设计
• 避免锁争用─ 减少锁定范围
─ 减少锁定时间
| 2004 JavaOneSM Conference | Session TS-233133
故障所在
• 考虑内存屏障─ 没有什么可提供内存屏障的影响
• 最初的双重检查用法─ AKA 多线程迟缓初始化
─ 任何非同步的非 volatile 型读 / 写引用
• 根据可见性的睡眠
• 关于数据争用的因果智能推断
| 2004 JavaOneSM Conference | Session TS-233134
线程局部对象的同步
• 线程局部对象的同步─ (仅由单一线程访问的对象)─ 没有语义或含义─ 编译器可以删除─ 也可以删除可重入同步
─例如,从同一对象的其它同步方法中调用某个同步的方法
• 这是人们暂时讨论的一种优化─ 不确定现在还是否有人使用
| 2004 JavaOneSM Conference | Session TS-233135
线程安全的迟缓初始化
• 要对许多线程共享的对象执行迟缓初始化
• 在初始化对象后,不要补偿同步
• 标准的双重检查锁定无法运行─ 使用检查的域 volatile 来修复
• 如果两个线程可能同时访问某个域,其中一个写入─ 域必须是 volatile
| 2004 JavaOneSM Conference | Session TS-233136
结尾
• 同步操作的成本非常高─ 但必要的同步成本很少
• 线程交互需要慎重考虑─ 不要太聪明─ 没有必要将重新排序考虑的很难
─程序中无数据争用,
无可见的重新排序
• 需要内部线程通信……
| 2004 JavaOneSM Conference | Session TS-233137
结尾—通信
• 线程间的通信─ 要求两个线程通过同步进行交互
• JSR-133 和 166提供了通信的新机制─ 高级并发性框架
─ Volatile 域
─ Final 域
| 2004 JavaOneSM Conference | Session TS-233138
问与答
java.sun.com/javaone/sf
| 2004 JavaOneSM Conference | Session TS-233139
最新 Java™ 技术内存模型
Jeremy Manson 和 William Pugh马里兰大学 http://www.cs.umd.edu/~pugh