我们不希望对每一次的内存访问都进行分析以确保程序是线程安全的,而是希望将一些现有的线程安全组件组合为更大规模的组件或者程序,这里介绍一些组合模式,这些组合模式能够使一个类更容易成为线程安全的,并且在维护这些类时不会无意中破坏类的安全性保证。

1、设计线程安全的类

  在设计线程安全类的过程中,需要包含以下三个基本要素:

  (1)、找出构成对象状态的所有变量。

  (2)、找出约束状态变量的不变性条件。

  (3)、建立对象状态的并发访问管理策略。

  对象的状态:如果对象中所得的域都是基本类型的变量,那么这些域将构成对象的全部状态;如果在对象的域中引用了其他对象,那么该对象的状态包括被引用对象的域。

1.1 收集同步需求

  对象与变量都有 一个状态空间,即所有的可能值。状态空间越小,就越容易判断线程状态。final类型的域使用得越多,就越能简化对象可能状态的分析过程。

  在许多类中都定义了一些不可变调条件(某个域的状态范围),用于判断状态是有效的还是无效的。

  同样,在操作中还包含一些后验条件判断状态迁移是否是有效的。如果counter当前状态是17,那么下一个状态只能是18.,当下一个状态需要依赖上一个状态时,这个操作必须是复合操作。

  由于不变性条件以及后验条件在状态以及 状态转换上施加了各种约束,因此就需要额外的同步和封装。

  如果不了解对象的不变性条件和后验条件,那么就不能确保线程安全性。要满足在状态变量的有效值和转换上的各种约束条件,就需要借助于原子性和封装性。

1.2 依赖状态操作

  在某些对象的方法中还包含一些基于状态的先验条件,例如不能从空队列中删除一个元素,在删除元素之前,必须得先判断该队列非空。

1.3 状态的所有权

  所有权在Java中只是一个设计中的要素,在语言层面没有明显的变现。所有权意味着控制权,如果发布了某个可变对象的引用,则意味着共享控制权。在定义哪些变量构成对象的状态时,只考虑对象拥有的数据。

2、实例封闭

  将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。

  被封闭的作用域可以是:

  (1)、一个实例中:作为一个私有成员

  (2)、某个作用域中:作为局部变量

  (3)、线程里:将对象从一个方法传递到另一个方法

2.1、Java监视器模式

  从线程封闭原子以及逻辑推论可以得到Java监视器模式。遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象的内置锁来保护。其可变状态都是私有的,并且涉企到该状态的方法都有一个内置锁来保护,而Java的内置锁也称为监视器锁或者监视器。在许多类中都使用了Java监视器模式,例如Vector和Hashtable。

3、线程安全性委托、独立的状态变量

  当一个对象有多个状态变量时,即多个域,并且每个状态变量没有耦合性,或者说不相互影响,我们就讲是独立的状态变量。当一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效的状态转换,那么可以将线程安全委托给底层的状态变量,只要每个独立的状态变量是线程安全的,那么整个类就是线程安全的。

  假如类的多个状态变量是相互影响的,即使每个状态变量都是线程安全的,那么整个类也有可能不是线程安全的。比如下面的代码:

 1 public class NumberRange {
 2     // 不变性条件: lower <= upper
 3     private final AtomicInteger lower = new AtomicInteger(0);
 4     private final AtomicInteger upper = new AtomicInteger(0);
 5     
 6     public void setLower(int i){
 7         if(i > upper.get()){
 8             throw new IllegalArgumentException("不能设置lower > upper");
 9         }
10         lower.set(i);
11     }
12     
13     public void setUpper(int i){
14         if(i < lower.get()){
15             throw new IllegalArgumentException("不能设置upper < lower");
16         }
17         upper.set(i);
18     }
19     
20     
21 }
View Code

相关文章: