线程安全类的设计
线程安全类的设计过程应包括以下三个基本要素:
• 识别构成对象状态的变量;
• 确定约束状态变量的不变量;
• 建立管理对对象状态的并发访问的策略。
使用私有锁对象而不是对象的内部锁(或任何其他可公开访问的锁)有很多优势。将锁对象设为私有可以封装锁,以便客户端代码无法获取它,而可公开访问的锁则允许客户端代码参与其同步策略——正确或不正确。
向现有线程安全的类添加功能
扩展比直接向类中添加代码更脆弱,因为同步策略的实现现在分布在多个单独维护的源文件中。如果底层类更改其同步策略(通过选择不同的锁来保护其状态变量),则子类将无声无息的出错,因为它不再使用正确的锁来控制对基类状态的并发访问。
错误案例
为什么这行不通?毕竟,putIfAbsent 是同步的,对吧?问题是它在错误的锁上同步。无论 List 使用什么锁来保护其状态,它肯定不是 ListHelper 上的锁。 ListHelper 只提供同步的假象;各种列表操作虽然都是同步的,但使用不同的锁,这意味着 putIfAbsent 相对于列表上的其他操作不是原子的。所以不能保证在执行 putIfAbsent 时另一个线程不会修改列表
修正:
如果扩展一个类以添加另一个原子操作是脆弱的,因为它将一个类的锁定代码分布在对象层次结构中的多个类上,那么客户端锁定就更加脆弱,因为它需要将类 C 的锁定代码放入与 C 完全无关的类中。
向现有类添加原子操作有一个不太脆弱的替代方法:组合。
ImprovedList 使用其自身的内在锁添加了额外的锁定级别。它不关心底层 List 是否是线程安全的,因为它提供了自己的一致锁定,即使 List 不是线程安全的或更改其锁定实现也能提供线程安全。虽然额外的同步层可能会增加一些小的性能损失。
实际上,我们使用了 Java 监视器模式来封装现有的 List,只要我们的类持有对底层 List 的唯一引用,就可以保证提供线程安全。