设计线程安全类的过程中,需要包含以下三个基本要素:
对象的状态
同步策略
规定了如何将不变性条件、线程封闭和加锁机制结合起来以维护线程的安全性,并且规定了哪些变量由哪些锁来保护
final类型的域使用的越多,越能简化对象可能状态的分析过程.
由于上述二条件施加的各种约束,因此就需要额外的同步与封装.
依赖状态:包含先验条件的操作
所有权在Java中只是一个类设计中的要素,在语言层面没有明显的表现.所有权意味着控制权,如果发布了某个可变对象的引用,则意味着共享控制权.在定义哪些变量构成对象的状态时,只考虑对象拥有的数据.
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁.
被封闭的对象一定不能超过它们既定的作用域.
对象可以封闭在类的一个实例(eg.私有成员)中,或者封闭在某个作用域内(eg.局部变量),再或者封闭在线程内.
实例封闭是构建线程安全类的一个最简单方式,还使得不同的状态变量可以由不同的锁来保护.
Java的包装器工厂(eg. Collections.synchronizedList.etc),只要包装器对象拥有对底层容器对象的唯一引用(即把底层容器对象封闭在包装器中),那么它就是线程安全的。对底层容器对象的所有访问必须通过包装器来进行。
当发布其他对象时,例如迭代器或内部的类实例,可能会间接地发布被封闭对象,同样会使被封闭对象逸出。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检查整个程序
遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护.
Java监视器模式的主要优势在于它的简单性.
多个变量之间是彼此独立,则可将线程安全性委托给多个状态变量.
即组合成的类不会在其包含的多个状态变量上增加任何不变性条件.
如果某个类含有复合操作,那么仅靠委托不足以实现线程安全性。在这种情况下,这个类必须提供自己的加锁机制以保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量。
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。
对于由Collections.synchronizedList封装的ArrayList,扩展类的功能,但并不是扩展类本身,而是将扩展代码放入一个”辅助类”中.
如下实现了一个包含”若没有则添加”操作的辅助类,用于对线程安全的List执行操作,但其中的代码是错误的.
@NotThreadSafe
class BadListHelper {
public List list = Collections.synchronizedList(new ArrayList());
public synchronized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if (absent)
list.add(x);
return absent;
}
}
要使其正确执行,必须使List在实现客户端外锁时使用同一个锁.
客户端加锁是指:对于使用某个对象的X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户代码.要使用客户端加锁,必须知道对象X使用的哪一个锁.