Java并发编程实战4章
第4章主要介绍如何构造线程安全类。
在设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量。
- 找出约束状态变量的不变性条件。
- 建立对象状态的并发访问管理策略。
构造线程安全类常采用的技术如下:
- 实例封闭
当一个对象被封装到另一个对象中时,能够访问被封装对象的所有代码路径都是已知的。与对象可以由整个程序访问的情况相比,更易于对代码进行分析。通过将封闭机 制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。
对象一般可以封闭在三种地方:
- 封闭在类的一个实例中,例如作为类的一个私有成员。
- 封闭在某个作用域内,例如作为一个局部变量。
- 封闭在线程内,例如在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象。
在Java平台的类库中有很多线程封闭的示例,比如一些基本的容器类并非线程安全的,例如ArrayList和HashMap,但类库提供了包装器工厂方法,使得这些非线程安全的类可以在多线程环境中安全地使用。
- 委托
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。
在现有的线程安全类中添加功能:
例子:假设需要一个线程安全的链表,它需要提供一个原子的“若没有则添加(put-if-Absent)”的操作
1.扩展
1 public class BetterVector<E> extends Vector<E> { 2 public synchronized boolean putIfAbsent(E x) { 3 boolean absent = !contains(x); 4 if (absent) { 5 add(x); 6 } 7 return absent; 8 } 9 }
这种方法的缺点是:导致了现在的同步策略实现被分布到多个单独维护的源代码文件中,一旦底层的类改变了同步策略并选择了不同的锁来保护它的状态变量,那么子类会被破坏,因为在同步策略改变后无法再使用正确的锁来控制对基类状态的并发访问。
2.客户端加锁机制
1 public class ListHelper<E> { 2 3 public List<E> list = Collections.synchronizedList(new ArrayList<E>()); 4 ... 5 public boolean putIfAbsent(E x) { 6 synchronized (list) { 7 boolean absent = !list.contains(x); 8 if (absent) { 9 list.add(x); 10 } 11 return absent; 12 } 13 } 14 15 }
这种方式很脆弱,因为它将类C的加锁代码放到与C完全无关的其他类中,会导致混乱的。
客户端加锁机制与扩展类机制的共同点:将派生类的行为与基类的实现耦合在一起,扩展破坏了实现的封装性,客户端加锁破坏了同步策略的封装性。
3.组合
1 public class ImprovedList<E> implements List<E> { 2 3 private final List<E> list; 4 5 public ImprovedList(List<E> list) { 6 this.list = list; 7 } 8 9 public synchronized boolean putIfAbsent(E x) { 10 boolean contains = list.contains(x); 11 if (contains) { 12 list.add(x); 13 } 14 return !contains; 15 } 16 17 public synchronized void clear() { 18 list.clear(); 19 } 20 //....按照类似的方式委托List的其他方法 21 }
这里其实使用了Java监视器模式来封装现有的List,并且只要在类中拥有指向底层List的唯一外部引用,就能确保线程安全性。
最后需要将同步策略文档化。