4.1 设计线程安全的类
通过使用封装技术,可以使得在不对整个程序进行分析的情况下就可以判断一个类是否是线程安全的。
在设计线程安全类的过程中,需要包含以下三个基本要素:找出构成对象状态的所有变量;找出约束状态变量的不变性条件;建立对象状态的并发访问管理策略。
要分析对象的状态,首先从对象的域开始。
同步策略(Synchronization Policy)定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同。同步策略规定了如何将不可变性、线程封闭与加锁机制结合起来以维护线程安全性,并且还规定了哪些变量有哪些锁来保护。
4.1.1 收集同步需求
要确保类的线程安全性,就需要确保它的不变性条件不会在并发的情况下被破坏,这就需要对其状态进行推断。对象与变量都有一个状态空间,即所有可能的取值。状态空间越小,就越容易判断线程的状态。final类型的域使用得越多,就越能简化对象可能状态的分析过程。
4.1.2 依赖状态的操作
类的不变性条件与后验条件约束了在对象上有哪些状态和状态转换是有效的。如果在某个操作中包含有基于状态的先验条件,那么这个操作就成为依赖状态的操作。
4.1.3 状态的所有权
许多情况下,所有权与封装性是相互关联的:对象封装它拥有的状态,反之也成立,即对它封装的状态拥有所有权。
容器类通常表现出一种“所有权分离”的形式,其中容器类拥有其自身的状态,而客户代码则拥有容器中各个对象的状态。
ServletContext为Servlet提供了类似于Map形式的对象容器服务,在Servlet中可以通过名称来注册(setAttribute)或获取(getAttribute)应用程序对象。有Servlet容器实现的ServletContext对象必须是线程安全的,因为它肯定会被多个线程同时访问。
4.2 实例封闭
如果某对象不是线程安全的,那么可以通过多种技术使其在多线程程序中安全地使用。你可以确保该对象只能由单个线程访问(线程封闭),或者通过一个锁来保护该对象的所有访问。
封装简化了线程安全类的实现过程,它提供了一种实例封闭机制(Instance Confinement)。当一个对象被封装到另一个对象中时,能够访问被封装对象的所有代码的路径都是已知的。通过将封装机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保变成在访问数据时总能持有正确的锁。
4.2.1 Java监视器模式
从线程封闭原则及其逻辑推论可以得出Java监视器模式。遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。
4.3 线程安全性的委托