对象的组合
如何才能设计一个线程安全的类?
找出构成对象状态的所有变量。
要创建一个线程安全的类,首先要明白哪些状态是需要同步的,就是要先收集同步需求。例如:当类中的一个变量的int值是10,每次加一,那么下一次只能是11。当下一个状态需要依赖当前状态时,这个操作必须是一个复合操作。该操作必须是一个原子操作。
找出约束状态变量的不变性条件。
类的不变性条件与后验条件约束了在对象上有哪些状态和状态转换是有效的,
在某些对象的方法中还包含一些基于状态的先验条件,先验条件在单线程中不满足便可退出,但是在并发环境下可以被其他线程唤醒,如果某个类的操作中包含基于状态的先验条件,那么这个操作就称为依赖状态的操作,可以通过现有库中的类(例如阻塞队列Blocking Queue或信号量)来实现依赖状态的行为。
建立对象状态的并发访问管理策略。
如何使一个非线程安全的类在多线程环境中使用?
- 线程封闭
- 实例封闭与合适的加锁机制配合(java监视器模式)
1 | public class Counter{ |
如果要委托线程安全的类,自身是否需要采用线程安全的策略?
当委托线程安全的类时,自身没有任何同步策略时,线程安全性是不确定额,视情况而定:
- 当委托了单个线程安全的类,并且没有施加额外的有效性条件时,类自身就是线程安全的。
- 当委托了多个线程安全的类,只要这些变量时彼此独立的,即组合而成的类并不会在其包含的多个状态变量上增加任何不变性条件,那么这个类就是线程安全的
- 当多个状态变量之间存在着某些不变性条件,状态变量之间有关联就会出现复合操作,那么仅依靠委托并不足以实现线程安全性,在这种情况下必须提供自己的加锁机制来保证复合操作都是原子性的。
- 如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量上的操作也不存在任何不允许的状态转换,那么就可以安全地发布这个变量了。
当保存同一个不变性条件中的所有变量时,要使用同一个锁。
创建一个新的线程安全的类往往要费时费力,大部分情况下,我们能使用已有的线程安全的类来完成我们的任务,但是,已有的类可能往往就缺少那么一个我们需要的方法,这时候,我们需要了解的是如何扩展一个已有的线程安全类?
- 最好的就是直接在原类中修改,使用原类的同步策略
- 扩展类机制:无法修改原类时,继承原类,并使用原类同步策略
- 客户端加锁机制:委托原类,并使用原类同步策略
- java监视器模式:委托原类,并定义自身同步策略
使用原类同步策略时,必须明确原类使用哪个锁来保护状态变量,但是这始终都是脆弱的,同步策略的实现被分布到多个单独维护的源文件中,一旦原类同步策略发生变化,扩展类都将被破坏。