Effective Java 第三版——82. 线程安全文档化
Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

82. 线程安全文档化
当并发使用一个类的方法时,类的行为方式是其与客户端建立约定的重要部分。如果未能文档化记录某个类行为的这一方面,其用户只能做出做出假设。如果这些假设是错误的,则生成的程序可能执行同步不够(条目 78)或过度同步(条目 79)。 无论哪种情况,都可能导致严重错误。
你可能听说过,可以通过在方法的文档中查找synchronized修饰符来判断该方法是否线程安全的。这在几个方面来讲是错误的。在正常操作中,Javadoc的输出中没有包含synchronized修饰符,这是有原因的。方法声明中synchronized修饰符的存在是实现细节,而不是其API的一部分。它不能可靠地说明方法是是线程安全的。
此外,声称存在synchronized修饰符就足以文档记录线程安全性,这体现了线程安全性是要么全有要么全无属性的误解。实际上,线程安全有几个级别。要启用安全的并发使用,类必须清楚地文档记录它支持的线程安全级别。下面的列表总结了线程安全级别。它并非详尽无遗,但涵盖以下常见情况:
不可变的(Immutable) —— 该类的实例看起来是不变的(constant)。不需要外部同步。示例包括String、Long和BigInteger(条目 17)。
无条件线程安全(Unconditionally thread-safe) —— 此类的实例是可变的,但该类具有足够的内部同步,以便可以并发使用其实例而无需任何外部同步。 示例包括AtomicLong和ConcurrentHashMap。
有条件线程安全(Conditionally thread-safe) —— 与无条件线程安全一样,但某些方法需要外部同步以便安全并发使用。 示例包括
Collections.synchronized包装器返回的集合,其迭代器需要外部同步。非线程安全(Not thread-safe) —— 这个类的实例是可变的。 要并发使用它们,客户端必须使用其选择的外部同步来包围每个方法调用(或调用序列)。 示例包括通用集合实现,例如ArrayList和HashMap。
线程对立(Thread-hostile) —— 即使每个方法调用都被外部同步包围,该类对于并发使用也是不安全的。线程对立通常是由于在不同步的情况下修改静态数据而导致的。没有人故意编写线程对立类;此类通常是由于没有考虑并发性而导致的。当发现类或方法与线程不相容时,通常将其修正或弃用。条目 78中的
generateSerialNumber方法在没有内部同步的情况下是线程对立的,如第322页所述。
这些分类(除了线程对立)大致对应于《Java Concurrency in Practice》一书中的线程安全注解,分别是Immutable,ThreadSafe和NotThreadSafe [Goetz06,附录A]。 上述分类中的无条件和条件线程安全类别都包含在ThreadSafe注解中。
在文档记录了一个有条件的线程安全类需要小心。 你必须指明哪些调用序列需要外部同步,以及必须获取哪个锁(或在极少数情况下是几把锁)才能执行这些序列。 通常是实例本身的锁,但也有例外。 例如,Collections.synchronizedMap的文档说明了这一点:
It is imperative that the user manually synchronize on the returned map when iterating over any of its collection views:
当迭代任何Map集合的视图时,用户必须手动同步返回的Map:Map<K, V> m = Collections.synchronizedMap(new HashMap<>());
Set<K> s = m.keySet(); // Needn't be in synchronized block
...
synchronized(m) { // Synchronizing on m, not s!
for (K key : s)
key.f();
}
不遵循此建议可能会导致不确定性行为。
类的线程安全性的描述通常属于类的文档注释,但具有特殊线程安全属性的方法应在其自己的文档注释中描述这些属性。 没有必要记录枚举类型的不变性。 除非从返回类型中显而易见,否则静态工厂必须在文档中记录返回对象的线程安全性,如Collections.synchronizedMap(上文)所示。
当类承诺使用可公开访问的锁时,它允许客户端以原子方式执行一系列方法调用,但这种灵活性需要付出代价。 它与并发集合(如ConcurrentHashMap)使用的高性能内部并发控制不兼容。 此外,客户端可以通过长时间保持可公开访问的锁来发起拒绝服务攻击。 这可能是偶然也可能是故意的。
要防止此拒绝服务攻击,可以使用私有锁对象而不是使用synchronized方法(这隐含着可公开访问的锁):
// Private lock object idiom - thwarts denial-of-service attack
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}
由于私有锁对象在类外是不可访问的,因此客户端不可能干扰对象的同步。 实际上,我们通过将锁定对象封装在它同步的对象中来应用条目 15的建议。
请注意,锁定属性(lock field)被声明为final。 这可以防止无意中更改其内容,从而导致灾难性的非同步访问(条目 78)。 我们通过最小化锁定属性(lock field)的可变性来应用条目 17的建议。 锁定属性(lock field)应始终声明为final。 无论使用普通的监视器锁(如上所示)还是使用java.util.concurrent.locks包中的锁,都是如此。
私有锁对象习惯用法只能用于无条件线程安全类。 有条件线程安全类不能使用这个习惯用法,因为它们必须文档记录在执行某些方法调用序列时客户端要获取的锁。
私有锁对象习惯用法特别适合用于为继承设计的类(条目 19)。 如果这样的类要使用其实例进行锁定,则子类可能容易且无意地干扰基类的操作,反之亦然。 通过为不同的目的使用相同的锁,子类和基类可能最终“踩到彼此的脚趾。”这不仅仅是一个理论问题;它就发生在Thread类上[Bloch05,Puzzle 77]中。
总之,每个类都应该用措辞严谨的描述或线程安全注解清楚地文档记录其线程安全属性。synchronized修饰符在本文档中没有任何作用。条件线程安全类必须文档记录哪些方法调用序列需要外部同步,以及在执行这些序列时需要获取哪些锁。如果你编写一个无条件线程安全的类,请考虑使用一个私有锁对象来代替同步方法。这将保护免受客户端和子类的同步干扰,并提供更大的灵活性,以便在后续的版本中采用复杂的并发控制方法。
Effective Java 第三版——82. 线程安全文档化的更多相关文章
- 《Effective Java 第三版》目录汇总
经过反复不断的拖延和坚持,所有条目已经翻译完成,供大家分享学习.时间有限,个别地方翻译得比较仓促,希望有疑虑的地方指出批评改正. 第一章简介 忽略 第二章 创建和销毁对象 1. 考虑使用静态工厂方法替 ...
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——7. 消除过期的对象引用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——9. 使用try-with-resources语句替代try-finally语句
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——12. 始终重写 toString 方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- checkbox、radio设置自定义样式
老生常谈,做一个简单的记录.浏览器自带的checkbox和radio样式可能不符合项目要求,通常要做一些自定义样式设置,目前基本的解决思路都是将input[type=checkbox/radio]隐藏 ...
- Ignatius and the Princess III HDU - 1028 -生成函数or完全背包计数
HDU - 1028 step 1:初始化第一个多项式 也就是 由 1的各种方案 组 成 的多项式 初始化系数为 1.临时区 temp初始化 为 0 step 2:遍历后续的n - 1 个 多项式 , ...
- BZOJ.4320.[ShangHai2006]Homework(根号分治 分块)
BZOJ \(\mathbb{mod}\)一个数\(y\)的最小值,可以考虑枚举剩余系,也就是枚举区间\([0,y),[y,2y),[2y,3y)...\)中的最小值(求后缀最小值也一样)更新答案,复 ...
- 【DWM1000】 非官方开源定位代码bitcraze
蓝点DWM1000 模块已经打样测试完毕,有兴趣的可以申请购买了,更多信息参见 蓝点论坛 正文: 最近关注DWM1000 定位,一方面在看DWM1000 官方提供的代码,也在四处网上找资料看资料. 其 ...
- 关于linux kernel slab内存管理的一点思考
linux kernel 内存管理是个很大的话题,这里记录一点个人关于slab模块的一点思考总结. 有些书把slab介绍成高速缓存,这会让人和cache,特别是cpu cache混淆,造成误解.sla ...
- [P4994]终于结束的起点 (递推)
终于结束的起点 终于写下句点 终于我们告别 终于我们又回到原点 …… 一个个 OIer 的竞赛生涯总是从一场 NOIp 开始,大多也在一场 NOIp 中结束,好似一次次轮回在不断上演. 如果这次 NO ...
- 1. Spring 简介以及关于 Eclipse 的 Spring Tool Suite 插件安装
今天开始学 Spring 了,就先来认识一下什么是 Spring 吧. 1. 首先,Spring 是一个框架,而且是开源的. 2. Spring 为简化企业级应用开发而生.使用 Spring 可以使简 ...
- PHP读写Excel
PHP读写Excel PHP读写Excel可以通过第三方库phpexcel比较优雅地完成,由于PHP对于字符串处理的优势,读写PHP非常方便. 库导入 这里使用composer包管理工具,以下是配置信 ...
- 学习 IOC 设计模式前必读:依赖注入的三种实现
一直以来就是越难的东西越值钱! 嘿嘿,这篇博文章转载自:http://www.cnblogs.com/liuhaorain/p/3747470.html 摘要 面向对象设计(OOD)有助于我们开发出高 ...
- Sublime_正则查找替换
在sublime编辑器中使用正则表达式对内容进行查找和替换: (1)Find——Replace... (2)出现下图界面 注意:点击左边第一个按钮,开启正则表达式功能. (3) (4)点击Replac ...