4.2 实例封闭
如果某个对象不是线程安全的,可以通过多种技术使其在多线程程序中安全地使用。比如线程封闭,或者通过一个锁来保护对该对象的所有访问。
实例封闭,就是把一个对象封装到另一个对象里面去,能够访问被封装对象的所有代码路径都是已知的,只要在这些路径上面设置合适的同步策略,就能确保以线程安全的方式来使用非线程安全的对象。
比如这个例子:
public class PersonSet {
private final Set<Person> mySet = new HashSet<Person>();
public synchronized void addPerson(Person p) {
mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
}
mySet不是线程安全的,但是它被封闭在PersonSet中不会逸出,唯一能访问mySet的代码路径是addPerson和containsPerson,它们被锁保护了,因此PersonSet是线程安全的。
Collections.synchronizedMap就是用的这种包装方式,这个静态方法在Map外层封装了一个具有访问控制的Map对象,从而使得Map变成了线程安全的。
4.3 线程安全性的委托
一个类可能设计为需要存储一些共享变量,为了实现这个类的线程安全性,可以向上面的实例封闭一样,添加一个线程安全层(也可以自己做这个安全层),或者可以尝试把线程安全性委托给已有的线程安全类。
实例封闭(添加安全层)的做法:
public class VisitRecord {
int visitTime = 0;
public synchronized void increase() {
visitTime++;
}
}
安全性委托的做法:
public class VisitRecord {
AtomicInteger visitTime = new AtomicInteger(0);
public void increase() {
visitTime.incrementAndGet();
}
}
经常被用作安全性委托的线程安全的类有:ConcurrentMap、BlockingQueue、Atomic系列等等。
如果有多个共享变量,那么就分两种情况:
1、共享变量间独立,那么仍然可以将每个共享变量使用线程安全类来保存。
2、共享变量间有不变性约束,那么仅靠委托就不足以实现线程安全性,这个类必须提供自己的加锁机制,以保证这些维护不变性条件的复合操作成为原子操作。
要点:
如果一个类是由多个独立且线程安全的状态变量组成的,那么可以将线程安全性委托给底层的状态变量。
如果一个状态变量时线程安全的,并且没有任何不变性条件来约束它的值,那么就可以安全地发布这个变量。
4.4 在现有的线程安全类中添加功能
四种方式的对比:
1、在原有的线程安全类中添加新功能。如果这些线程安全类是java api,那么显然不能做到。如果是自己工程内部实现的类,那么这将是最好的选择。
如果很清楚线程安全的类,是用的this锁的话,还可以这样:
2、类扩展,添加同步方法。这样做的坏处是,同步策略被分散了,如果底层的类修改了同步策略,或者选择了不同的锁来保护它的状态变量,那么子类会被破坏。
3、客户端加锁。和类扩展是一样的,只是组合了底层对象,并且在新的方法里面,显式的使用了synchronized(底层对象)。缺点同上。
或者不依赖旧类的线程安全性,添加自己的线程安全层:
4、组合。将底层对象作为自己的属性,并且进行实例封闭(不管它是不是线程安全的)。这种方法的好处就是不再有任何安全性的依赖。坏处就是相当于完全重写了同步策略,工作量上去了。