对象的组合
通过一些安全的组件和程序组合成更大规模的组件和程。这里介绍一些组合的模式,这个模式能够使一个类更容易成为线程安全的,并且在维护这些类时不会无意中破坏类的安全性保证。
设计线程安全的类
设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量。
- 找出约束状态变量的不可变性条件。
- 建立对象状态的并发访问管理策略。
收集同步需求
要确保类的安全性,就需要确保它的不变性条件不会在并发访问的情况下被破坏,这就需要对其状态进行推断。
依赖状态的操作
我们在执行一些操作的时候往往需要检查一些条件,称为先验条件。在Java中,等待某个条件为真的各种内置机制(包括等待和通知等机制)都与内置加锁紧密关联。这并不容易,要想实现某个等待先验条件为真时才执行的操作,一种简单的方式是通过库中的类(例如阻塞队列【Blocking Queue】或者信号量【Semaphore】)来实现依赖状态的行为。
状态的所有权
要保证对象的所有权
实例封闭
封装简化了线程安全类的实现过程。如果要保证一个类的线程安全,那么属性对象最好也是线程安全的。
我们常用的一些集合类未必是线程安全的,可以使用Collections.synchronizedList及其类似的方法来保证线程安全性。
封装机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性就无需检查整个程序。
java监视器模式
遵循java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。Vector和HashTable是有这种模式的。
使用锁对象而不是对象的内置锁有很多优点。
线程安全性的委托
如果对象中的所有的属性都是线程安全的,并不代表你这个对象就一定是线程安全的,需要根据情况加锁同步。
独立的状态变量
这时,我们不需要单独加锁同步,因为对象里的属性是不耦合的而且是线程安全的。
当委托失效时
如果一个类是由多个独立且线程安全的状态组成,并且在所有操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。
发布底层的状态变量
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全的发布这个变量。
换句话说就是这个变量的修改对其他地方不影响。
在现有的线程安全类中添加功能
java类库中有许多现有的基础模块类。这样可以降低开发风险(你写的类可能不好)。但有时确实需要扩展功能。
思路有3:
- 修改原有的类(很难做到,可能搞乱了类库有隐患)
- 客户端加锁,(会破坏封装性)
- 定义一个类封装这个类,也就是包装类。
- 还有就是扩展这个类,但是删除原来类的一个方法可能实现起来需要看看原来的类是否允许这么干。
客户端加锁
这种直接加锁的方式,比如使用Collections.synchronizedList加锁,可能只是同步假相,因为这里使用的锁和内部的锁可能并不一样。
通过一个原子操作来扩展类是脆弱的,因为它将类的加锁代码分布到多个类中。然而,客户端加锁却更加脆弱。客户端加锁同样会破坏同步策略的封装性。
组合
为现有的类添加原子操作的更好的办法是组合。不管属性是否安全,只在这个类中进行又一层的同步。
将同步策略文档化
在文档中说明客户端代码需要了解的线程安全性保障,以及代码维护人员需要了解的同步策略。
我们也需要从文档中读取相关信息。
有的第三方库没有文档,那么需要看看代码,没有代码的话,就需要去猜测。