Broadview 并发编程实践 JAVA CONCURRENCY IN PRACTICE 日RIAN GOETZ Brian Goetz Tim Peierls AND DOUG LEA [美] Joshua Bloch Joseph Bowbeer· 著 David Holmes Doug Lea JAVA 韩锴 方妙 译 C9S4座 電子工紫出领社
序 Pretace 写作本书时,出于桌面系统的迫切需求,多核处理器正在变得越来越便宜。与此不协 调的是,很多开发团队还没有注意到,在他们的项目中,出现了越来越多的关于线程的错 误报告。在NetBeans开发者站点上最近的一次通告中,一位核心维护者注意到,为了修 复某个类的一个线程相关的问题,已经被打了l4次补丁。Dion Almaer,前TheServerSide 的编辑(经过一次痛苦的调试过程,最终发现是一个线程的bug之后),最近在Bog上 写道,大多数Java程序都充斥着并发bug,它们仅仅是“碰巧”可以工作。 的确,由于并发性的g不会以可预见的方式自己“蹦”出来,因此多线程程序的开 发、测试和调试都会变得极端困难。bug浮出水面的时刻,通常可能是最坏的时候一一对 应于生产环境,就是指在高负载的时候。 使用Java开发并发程序所要面对的挑战之一,是要面对平台提供的各种并发特性之间 的不匹配,还有就是程序员在他们的程序中应该如何思考并发性。语言提供了一些低层机 制,比如同步和条件等待,但是这些机制在实现应用级的协议与策略时才是必须的。不顾 这些策略的约束,很容易创建出一个程序,它在编译和运行时看上去一切正常,不过这其 中却存在隐患。很多并发方面相当不错的书都没能达到预期的目标,它们过分地关注于低 层的机制和API,而不是设计层面的策略和模式。 Java5.0是在使用Java开发并发应用程序的进程中,迈出的巨大一步。它提供了新的 高层组件以及更多的低层机制,这些将使得一名新手更容易像专家那样去构建并发应用程 序。本书的作者都是JCP专家组的主要成员,正是这个专家组创建了这些新工具;除了去 描述新工具的行为和特性,我们还向您展示了它们低层的设计模式,预期的使用场景以及 将它们纳入平台核心库的动机。 我们的目标是给读者一些设计法则和理念模型,让读者在使用Java构建正确、高效的 并发类和应用程序时,变得更容易、更有趣。 我们希望你在阅读《JAVA并发编程实践》的过程中能够获得偷悦感。 Brian Goetz 2006年3月于Williston,VT Jav阳并发编程实践
VI 序 如何使用本书 Java低层机制与设计层必要的策略之间存在着抽象的不匹配。为了解决这个问题,我 们呈现了一个经过简化的规则集,用来编写并发程序。专家们看到这些规则会说:“嗨, 这可不是完整的规则,类C即使破坏了规则R,它仍然可以是线程安全的。”尽管打破我 们的规则,也可能写出正确的并发程序,不过这样做需要对Java存储模型的低层细节有着 深入的理解。我们的愿望是:开发者不用熟悉这些细节,就有能力编写正确的并发程序。 只要坚持遵守我们简单的规则,就能编写出正确的、可维护的并发程序。 我们假设读者已经具备了对Java基本并发机制的一些了解。《JAVA并发编程实践》 不是并发的入门指南一关于这个,可以参看任何正统的介绍性的大部头书籍,比如《Java 编程语言》(Arnold et al.,2005)。本书也不是关于并发的百科全书似的参考手册一一关 于这个,可以参看《JAVA并发编程(Lea,2000)》。对本书更恰当的描述是,它提供了 实际的设计规则,可以协助开发者,他们正在处于创建安全的、高效的并发类这一艰难的 过程中。在适当的地方,我们穿插引用了下列图书的章节:《The Java Programming Language》、《Concurrent Programming in Java》)、《The Java Language Specification(Gosling etal,2005)》以及《Effective Java(Bloch,.2001)》,并使用[JPLn.m]、[CPJn.m]、[JLS n.m]和[EJ Item n来表示它们。 结束“介绍”(第1章)之后,本书分为4部分: 基础。第1部分(第2~5章)关注于同步和线程安全的基本概念,以及如何使用类 库提供的构建块组合线程安全类。第110页上,有一个“并发诀窍清单”总结了出现在第 1部分中最重要的规则。 第2章(线程安全性)与第3章(共享对象)构成了全书的基础。几乎所有用来避免 并发危险、创建线程安全类以及验证线程安全的规则都包括在这里。轻“理论”,重“实 践”的读者可能会禁不住诱惑跳过这部分,而直接进入第2部分,但是在开始编写任何并 发代码之前,一定要确保回过头来阅读这两章!第4章所涵盖的技术,用于把线程安全类 组合到更大的线程安全类中。第5章(构建块)涵盖了平台核心库提供的并发构建块一 线程安全的容器和同步工具(synchronizer).。 构建并发应用程序。第2部分(第6~9章)描述了如何利用线程提高并发应用程序的 吞吐量或响应性。第6章(任务执行)讲述如何识别可并行执行的任务,并在任务执行框架 内部执行它们。第7章讲到的技术可以让任务和线程在正常终止之前妥善地终止;区分并发 应用程序是健壮的,还是仅仅可以将就工作的,有众多的因素,“程序如何处理取消与关闭” 就是其中之一。第8章(应用线程池)关注了一些任务执行框架中更为高级的特性。 Java并发编程实践
序 IX 第9章(GUI应用程序)关注了用来提高单线程化子系统响应性的技术。 活跃度、性能和测试。第3部分(第10~12章)涉及并发程序自身。要确保并发程 序执行了你所希望它做的事情,而且性能是可以接受的。第10章(避免活跃度危险)描 述了如何避免活跃度失败,活跃度失败会阻止程序继续向前执行。第11章(性能和可伸 缩性)涵盖的技术用来提高并发代码的性能和可伸缩性。第12章(测试并发程序)涵盖 的技术用来测试并发代码的正确性和性能。 高级主题。第4部分(第13~16章)涵盖的主题可能只会引起资深程序员的兴趣: 它们是显式锁、原子变量、非阻塞算法和开发自定义的synchronizer.. 代码示例 尽管书中的很多通用概念适用于Java5.0之前版本,甚至是非Java环境,不过大多数 示例代码(以及关于Java存储模型的每一句话)都假定是以Java5.0或更新的JDK为基 础的。有些代码示例还会用到Java6中添加到类库中的特性。 代码示例已经被裁减,以降低它们的尺寸和突出相关的部分。完整版本的代码示例、 辅助示例和勘误表,可以从本书的网站http:/hwww,javaconcurrencyinpractice..con上获得。 代码示例分为三类:“好”示例,“一般”示例和“坏”示例。“好”示例阐释的技 术应该被效仿。“坏”示例阐释的技术绝对不应该被效仿,而且还会用一个“Mr.Yk”1 的图标清楚地表明这是“有害”的代码(参见清单1)。“一般”示例阐释的技术不一定 是错的,但却是是脆弱的、有风险的或者执行效果差的,而且会用一个“Mr CouldBeHappier”图标标识出来,如同清单2。 清单1糟糕的列表排序方法(不要这样做) public >void sort (Listlist) //永远不要返回错误的答聚 System.exit(0); 有些读者会质疑“坏”示例在本书中的角色;毕竟,一本书应该展现如何做正确的事, 而不是错误的事。“坏”示例有两个目的。它们揭示了常见的缺陷,更重要的是它们示范 了如何分析程序的线程安全性一完成此事的最佳办法就是观察威胁线程安全的各种方 式。 IMr.Yuk是the Children's Hospital of Pittsburgh的注册商标,以授权在本书中使用 Java并发编程实践
序 清单2缺少优化的列表排序方法 public >void sort(Listlist){ for(1nt1=0;1<1000000:1++) doNothing(); Collections.sort(list); 致谢 java.util.concurrent包的开发过程催生了本书。这个包是由Java Community Process JSR166创建的,后被加入到Java5.0中。还有很多人为JSR166作出过贡献:我 们尤其要感谢Martin Buchholz,他完成了所有把代码植入JDK的相关工作:还要感谢 concurrency-interest邮件清单的所有读者,他们为API的草案提供了他们的建议和反馈。 本书能够大幅度地完善,得益于大量的建议和帮助,这些帮助来自多方面的人员。我 们要感谢Dion Almaer、Tracy Bialik、Cindy Bloch、Martin Buchholz、Paul Christmann、Cliff Click、Stuart Halloway、David Hovemeyer、Jason Hunter、Michael Hunter、Jeremy Hylton、 Heinz Kabutz、Robert Kuhar、Ramnivas Laddad、Jared Levy、Nicole Lewis、Victor Luchangco、 Jeremy Manson、Paul Martin、Berna Massingill、Michael Maurer、Ted Neward、Kirk Pepperdine、Bill Pugh、Sam Pullara、Russ Rufer、Bill Scherer、Jeffrey Siegal、Bruce Tate、 Gil Tene、Paul Tyma,以及硅谷模式小组的成员,他们通过很多饶有趣味的技术交流,提 供了指南,提出了建议,这些对本书能够做到更好颇有帮助。 我们格外感激Cliff Biffle、Barry Hayes、Dawid Kurzyniec、Angelika Langer、Doron Rajwan和Bill Venners,.他们审阅了全部手稿,还关注了令人烦恼的细节,寻找代码示例 中的bug,并且提出了大量令本书得以改进的建议。 我们感谢Katrina Avery,他出色地完成了copy-editing的工作:Rosemary Simpson在 极端的时间压力下,还制作了索引。我们感谢Ami Dewar绘制的插图。 感谢Addison-Wesley的全体组员,他们让这本书得以问世。Ann Sellers让项目得以运 行;Greg Doench让项目的进度有条不紊;Elizabeth Ryan一直领导着本书写作的进程。 我们还想要感谢成千上万的软件工程师,他们开发了编写本书所用到的软件,间接地 为本书作出了贡献。这些软件包括TEX、LATEX、Adobe Acrobat、pic、grap、Adobe Illustrator、Perl、Apache Ant、IntelliJ IDEA、GNU emacs、Subversion、TortoiseSVN,当 然,还有Java平台与类库。 Java并发编程实践
目录 代码清单 44514084400484444444, 第1章介绍. 1.1并发的(非常)简短历史 1 1.2线程的优点… 3 1.3 线程的风险.… 1.4线程无处不在… .9 第1部分基础.… .13 第2章线程安全… .15 2.1什么是线程安全性 17 2.2原子性 .19 2.3锁. 23 2.4用锁来保护状态 .27 2.5活跃度与性能 29 第3章共享对象 33 3.1可见性 33 3.2发布和逸出 39 3.3线程封闭.… 42 3.4不可变性.… 46 3.5安全发布 49 第4章组合对象 .55 4.1设计线程安全的类 55 4.2实例限制… .58 4.3委托线程安全… 62 4.4向已有的线程安全类添加功能 .71 Java并发编程实践
xvi 目录 4.5同步策略的文档化… …74 第5章 构建块 .79 5.1同步容器… .79 5.2并发容器 .84 5.3阻塞队列和生产者-消费者模式 …87 5.4阻塞和可中断的方法.… 92 5.5 Synchronizer.... 94 5.6为计算结果建立高效、可伸缩的高速缓存 101 第2部分构建并发应用程序 .111 第6章任务执行.… .113 6.1在线程中执行任务… .113 6.2 Executor框架 .117 6.3寻找可强化的并行性 .123 第7章取消和关闭… .135 7.1任务取消… 135 7.2停止基于线程的服务 150 7.3处理反常的线程终止… 161 7.4JVM关闭… 164 第8章应用线程池.… .167 8.1任务与执行策略间的隐性耦合 167 8.2定制线程池的大小… 170 8.3配置ThreadPoolExecutor.… 171 8.4扩展ThreadPoolExecutor.. 179 8.5并行递归算法… 181 第9章GUI应用程序. …189 9.1为什么GUI是单线程化的 189 9.2短期的GU1任务… 192 9.3耗时GUI任务 195 9.4共享数据模型… 198 9.5其他形式的单线程子系统… .202 Jva并发编程实践
目录 xvii 第3部分 活跃度,性能和测试 .203 第10章 避免活跃度危险 205 10.1死锁 205 10.2避免和诊断死锁 215 10.3其他的活跃度危险… 218 第11章性能和可伸缩性 .221 11.1性能的思考… 221 11.2 Amdahl定律. .225 11.3线程引入的开销… .229 11.4减少锁的竞争… 232 11.5示例:比较Map的性能 242 11.6减少上下文切换的开销 243 第12章测试并发程序 247 12.1测试正确性 248 12.2测试性能.… 260 12.3避免性能测试的陷阱 266 12.4测试方法补遗… 270 第4部分 分高级主题.275 第13章 章显式锁..277 13.1L0ck和ReentrantL0ck.… 277 13.2对性能的考量… 282 13.3公平性… 283 13.4在synchronized和ReentrantL0ck之间进行选择 285 13.5读-写锁… 286 第14章构建自定义的同步工具 291 14.1管理状态依赖性… 291 14.2使用条件队列 298 14.3显式的Condition对象 306 14.4剖析Synchronizer… 308 14.5 AbstractQueuedSynchronizer..... 311 14.6jaVa.util.concurrent的Synchronizer类中的AQS..314 Java并发编程实践
xviii 目录 第15章原子变量与非阻塞同步机制 .319 15.1锁的劣势 319 15.2硬件对并发的支持 .321 15.3原子变量类… …324 15.4非阻塞算法… ..329 第l6章Java存储模型 .337 16.1什么是存储模型,要它何用 337 16.2发布. 344 16.3初始化安全性. 349 附录A同步Annotation..… ,,,,,3 A.l类Annotation… 353 A.2域Annotation和方法Annotation 353 参考文献355 Java并发编程实践
代码清单 清单1糟糕的列表排序方法(不要这样做) ...X 清单2缺少优化的列表排序方法… X 清单1.1非线程安全的序列生成器 .6 清单】2线程安全的序列生成器7 清单2.1一个无状态的S沦Vlet]8 清单2.2 Servlet计算请求数量而没有必要的同步(不要这样做) .19 清单2.3惰性初始化中存在竞争条件(不要这样做) 21 清单2.4 Servlet使用AtomicL0ng统计请求数.23 清单2.5没有正确原子化的Servlet试图缓存它的最新结果(不要这样做)…24 清单2.6缓存了最新结果的servlet,但响应性令人无法接受(不要这样做) .26 清单2.7如果内部锁不是可重入的,代码将死锁… 27 清单2.8缓存最新请求和结果的servlet.31 清单31在没有同步的情况下共享变量(不要这样做) 34 清单3.2非线程安全的可变整数访问器 .36 清单3.3线程安全的可变整数访问器 36 清单3.4数绵羊… 4.39 清单3.5发布对象 .40 清单3.6允许内部可变的数据逸出(不要这样做) 40 清单3.7隐式地允许this引用逸出(不要这样做) .41 清单3.8使用工厂方法防止his引用在构造期间逸出… …42 清单3.9本地的基本类型和引用类型的变量的线程限制 .44 清单3.l0使用Thread Local确保线程封闭性 .45 清单3.11构造于底层可变对象之上的不可变类.47 清单3.12在不可变的容器中缓存数字和它的因数. 49 清单3.l3使用到不可变容器对象的volatile类型引用,缓存最新的结果 .50 清单3.14在没有适当的同步的情况下就发布对象(不要这样做) 50 清单3.15如果Holder没有被正确发布,它将处于失败的风险中.51 清单4.1使用Java监视器模式的简单线程安全计数器 .56 清单4.2使用限制确保线程安全 …59 清单4.3私有锁保护状态… 6 Java并发编程实践