1 设计线程安全的类
—在设计线程安全类的过程中,需要包含以下三个基本要素:
· 找出构成对象状态的所有变量。
· 找出约束状态变量的不变性条件。
· 建立对象状态的并发访问管理策略。
—对象的状态:如果对象中所有的域都是基本类型的变量,那么这些域将构成对象的全部状态;如果在对象的域中引用了其他对象,那么该对象的状态将包含被引用对象的域。
—同步策略定义了如何在不违背对象不变条件和后验条件的情况下对其状态的访问操作进行协同。
—如果不了解对象的不变性条件和后验条件,那么就不能确保线程安全性。要满足在状态变量的有效值或状态转换上的各种约束条件,就需要借助于原子性和封装性。
—依赖状态的操作:如果在某个操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作。

2 实例封闭
—封装简化了线程安全类的实现过程,它提供了一种实例封闭机制,通常也简称为“封闭”。
—将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
—被封闭对象一定不能超出它们既定的作用域。对象可以封闭在类的一个实例(例如作为类的一个私有成员),或者封闭在某个作用域内(例如作为一个局部变量),再或者封闭在线程内(例如在某个线程中将对象从一个方法传递到另一个方法,而不是在多个线程之间共享该对象)。
—实例封闭是构建线程安全类的一个最简单方式,它还使得在锁策略的选择上拥有了更多的灵活性,实例封闭还使得不同的状态变量可以由不同的锁来保护。
—实例封闭的例子:类库中提供了一些包装器工厂方法(例如Collections.synchronizedList及其类似方法),这些工厂方法通过“装饰器”模式将容器类封装在一个同步的包装器对象上,而包装器能将接口中的每个方法都实现为同步方法,并将调用请求转发到底层的容器对象上。并且,包装器对象拥有对底层容器对象的唯一引用,也即把底层容器对象封闭在包装器中。
—如果将一个本该被封闭的对象发布出去,那么就会破坏封闭性;而且,在发布其他对象时,例如迭代器或内部的类实例,可能会间接地发布被封闭对象,同样会使被封闭对象逸出。

3 Java监视器模式

—从实例封闭原则及其逻辑推论可以得出Java监视器模式。

—遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。示例如下:

java对象的组合与复用 java中对象的组合_实例封闭


—在许多类中都使用了Java监视器模式,如Vector和Hashtable。

—Java监视器模式的主要优势在于它的简单性。

—使用私有的锁对象而不是对象的内置锁(或任何其他可通过公有方式访问的锁),有许多的优点:

· 私有的锁对象可以将锁封装起来,使客户代码无法得到锁,但客户代码可以通过公有方法来访问锁,以便参与到它的同步策略中。

· 如果客户代码错误地获得了另一个对象的锁,那么可能会产生活跃性问题。

· 要想验证某个公有访问的锁在程序中是否被正确地使用,则需要检查整个程序,而不是单个的类。

4 线程安全性的委托
—如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。
—如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。

5 在现有的线程安全类中添加功能

(1)修改原始的类

—优点:实现同步策略的所有代码仍然处于一个源代码文件中,从而更容易理解与维护。

—缺点:通常无法做到,因为可能无法访问或修改类的源代码。

(2)扩展这个类

—例子:扩展Vector类,如下:

java对象的组合与复用 java中对象的组合_java对象的组合与复用_02


—缺点:

· 并非所有类都将状态向子类公开。因此不适合采用这种方法;

· 由于现在的同步策略实现被分布到多个单独维护的源代码文件中,因此该方法比直接将代码添加到类中更加脆弱。

· 扩展将派生类的行为与基类的实现耦合在一起,会破坏实现的封装性。

(3)客户端加锁机制

—实现思路:扩展类的功能,但并不是扩展类本身,而是将扩展代码放入一个“辅助类”中。

—在实现客户端加锁时必须使用同一个锁。

—客户端加锁是指,对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户代码。

—缺点:

· 和扩展类的方法相比更加脆弱,因为它将类C的加锁代码放到与C完全无关的其他类中。

· 和扩展类的方法类似,客户端加锁也会将辅助类与基类的实现耦合在一起,破坏同步策略的封装性。

—例子:对于由Collections.synchronizedList封装的ArrayList的扩展,例子如下:

java对象的组合与复用 java中对象的组合_Java监视器模式_03


(4)组合

—如下所示,ImprovedList通过将List对象的操作委托给底层的List实例来实现List的操作,同时还添加了一个原子的putIfAbsent方法。

java对象的组合与复用 java中对象的组合_java对象的组合与复用_04


—实现思路:ImprovedList通过自身的内置锁增加了一层额外的加锁,它并不关心List是否是线程安全的,即使List不是线程安全的或者修改了它的加锁实现,ImprovedList也会提供一致的加锁机制来实现线程安全性。

—缺点:可能会导致轻微的性能损失。

—优点:和模=模拟另一个对象的加锁策略相比,该方法更为健壮。