在设计线程安全类的过程中,需要包含以下三个基本要素:
要分析对象的状态,首先从对象的域开始。如果对象中所有的域都是基本类型的变量,那么这些域将构成对象的全部状态。如果对象的域中引用了其他对象,则该对象的域包含被引用对象的域。
同步策略定义了如何在不违背对象的不变性条件和后验条件的情况下对其状态的访问操作进行协同。同步策略规定了如何将不可变性、线程封闭与加锁机制等结合起来以维护线程的安全性,并且还规定了哪些变量由哪些锁来保护。
在许多类中都定义了一些不可变条件,用来判断状态是有效的还是无效的。例如long类型的变量,其状态空间是Long.MIN_VALUE到Long.MAX_VALUE。但我们定义了一个类,该类中有一个long类型的计数器,则该long类型的变量存在一个限制,即不能为负值。
同样,在操作中还会包含一些后验条件来判断状态迁移是否是有效的。如计数器当前值为17,那么下一状态只能是16或18.当下一个状态需要依赖当前状态时,这个操作就必须是一个复合操作。
由于不变性条件和后验条件在状态和状态转换上添加了与许多限制,因此就需要额外的同步和封装。
如果不了解对象的不可变条件和后验条件,那么就不能确保线程安全性。要满足各种约束条件,就需要借助于原子性与封装性。
类的不变性条件和后验条件约束了在对象上有哪些状态和状态转换是有效的。在某些对象的方法中还包含一些基于状态的先验条件。例如不能从空队列中删除一个元素。如果在某个操作中包含有基于状态的先验条件,那么这个操作就被称为依赖状态的操作。
大多数对象都是组合对象。当从头开始构建一个类或者将多个非线程安全的类组合成一个类时,监视器模式非常有用。但如果类中各个组件已经是线程安全的,会是什么情况?在某些情况下通过多个线程安全类组合而成的类是线程安全的,而在某些情况下不是。
如果某个类含有复合操作,那么仅靠委托并不足以实现线程安全性。在这种情况下这个类必须提供自己的加锁机制以保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量。
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效的状态转换,那么可以将线程安全性委托给低层的状态变量。
当把线程安全性委托给某个对象的底层状态变量时,什么条件下可以发布这些变量从而使其他类可以修改它们?答案仍然取决于在类中对这些变量施加了什么不变性条件。
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。