第4章 对象的组合

4. 对象的组合

将一些现有的线程安全组件组合为更大规模的组件或程序

【先验条件】(precondition) 针对方法(method),它规定了在调用该方法之前必须为真的条件。

【后验条件】(postcondition) 也是针对方法,它规定了方法顺利执行完毕之后必须为真的条件。

4.1 设计线程安全的类

基本要素:

  • 找出构成对象状态的所有变量
  • 找出约束状态变量的不变性条件
  • 建立对象状态的并发访问管理策略
@ThreadSafe
public final class Counter {
    @GuardedBy("this") private long value = 0;
    
    public synchronized long getValue() {
        return value;
    }
    
    public synchronized long increment() {
        if (value == Long.MAX_VALUE)
            throw new IllegalStateException("counter overflow");
        // 后验条件
        return ++value;
    }
}

同步策略(Synchronization Policy)定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同。

4.1.1 收集同步需求

对状态进行推断,确保不变条件不会在并发访问的情况下被破坏。对象与变量都有一个状态空间,即所有可能的取值。状态空间越小,越容易判断线程的状态。final类型的域使用的越多,越能简化对象可能状态的分析过程。

操作中包含一些后验条件来判断状态迁移是否有效的,当下一个状态需要依赖当前状态时,这个操作必须是一个复合操作。

4.1.2 依赖状态的操作

类的不变条件与后验条件约束了在对象上有那些状态和状态转换时有效的。先验条件,移除队列一个元素前,队列处于非空状态。

4.1.3 状态的所有权

如果以某个对象为根节点构造一张对象图,那么该对象的状态将是对象图中所有对象包含的域的一个子集。

垃圾回收机制避免了如何处理所有权的问题。

容器类(Servlet)通常表现出一种“所有权分离”的形式,其中容器类拥有其自身的状态,而客户代码则有容器中各个对象的状态。

4.2 实例封闭

某对象不是线程安全的,那么可以通过多种技术使其在多线程程序中安全的使用。

封装简化了线程安全类的实现过程,它提供一种实例封闭机制(Instance Confinement),简称为封闭。封闭机制与合适的加锁策略结合起来,可以确保线程安全的方式使用非线程安全对象。

@ThreadSafe
public class PersonSet {
    @GuardedBy("this")
    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);
    }
}

封闭机制更易于构造线程安全的类,因为当时封闭类的状态时,在分析类的线程安全性时就无须检查整个程序

4.2.1 Java监视器模式

从线程封闭原则及其逻辑推论可以得出Java监视器模式。遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。

public class PrivateLock {
    private final Object myLock = new Object();
    @GuardedBy("myLock") Widget widget;
    
    void someMethod() {
        synchronized (myLock) {
            
        }
    }
}

4.2.2 示例:车辆追踪

@ThreadSafe
public class MonitorVehicleTracker {
    @GuardedBy("this")
    private final Map<String, MutablePoint> locations;
    
    public MonitorVehicleTracker() {
        
    }
}

4.3 线程安全性的委托

当从头开始构建一个类,或者将多个非线程安全的类组合成为一个类时,Java监视器模式时非常有用的。

4.3.1 示例:基于委托的车辆追踪器

将线程安全委托给ConcurrentHashMap

@Immutable
public class Point {
    public final int x, y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

@ThreadSafe
public class DelegatingVehicleTracker {
    private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;
    
    public DelegatingVehicleTracker(Map<String, Point> points) {
        locations = new ConcurrentHashMap<String, Point>(points);
        unmodifiableMap = Collections.unmodifiableMap(locations);
    }
    
    public Map<String, Point> getLocations() {
        return unmodifiableMap
    }
    
    public Point getLocation(Stirng id) {
        return locations.get(id);
    }
    
    public Map<String, Point> getLocations() {
        return Collections.unmodifiableMap(new HashMap<String, Point>(locations));
    }
    
    public void setLocation(Stirng id, int x, int y) {
        if (locations.replace(id, new Point(x, y)) == null) {
            throw new IllegalArgumentException("invalid vehicle name:" + id);
        }
    }
}

4.3.2 独立的状态变量

状态变量彼此独立,组合而成的类不会在其包含的多个状态变量上增加任何不变性条件

public class VisualComponent {
    private final List<KeyListener> keyListeners = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners = new CopyOnWriteArrayList<MouseListener>();
    
    public void add(KeyListener listener) {
        keyListeners.add(listener);
    }
    
    public void add(MouseListener listener) {
        mouseListeners.add(listener);
    }
    
    public void remove(KeyListener listener) {
        keyListeners.remove(listener);
    }
    
    public void remove(MouseListener listener) {
        mouseListeners.remove(listener);
    }
}

4.3.3 当委托失效

NuberRange类不足以保护它的不变性条件,没有维持对下界和上界进行约束的不变性条件

public class NumberRange {
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);
    
    public void setLower(int i)
        if (i > upper.get())
            thron new IllegalArgumentException("can't set lower to " + i + " > upper");
        lower.set(i);
    }
    
    public void setUpper(int i) {
        if (i < lower.get())
            throw new IllegalArgumentException("can't set upper to " + i + " < lower");
        upper.set(i);
    }
	
	public boolean isInRange(int i) {
        return (i >= lower.get() && i <= upper.get());`
    }
}

4.3.4 发布底层的状态变量

如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在不允许的状态转换,那么就可以安全的发布这个变量

4.3.5 示例:发布状态的车辆追踪器

如果在车辆位置有效值上施加任何约束,如需要对车辆位置的变化进行判断或者当位置变化时执行一些操作,那么就不是线程安全的

@ThreadSafe
public class SafePoint {
    @guardedBy("this") private int x, y;
    
    private SafePoint(SafePoint p) { this(p.get()); }
    
    public SafePoint(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public synchronized int[] get() {
        return new int[] {x, y};
    }
    
    public synchronized void set(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

@ThreadSafe
public class PublishingVehicleTracker {
    private final Map<String, SafePoint> locations;
    private final Map<String, SafePoint> unmodifiableMap;
    
    public PublishingVehicleTracker(Map<String, SafePoint> locations) {
        this.locations = new ConcurrentHashMap<String, SafePoint>(locations);
        this.unmodifiableMap = Collections.unmodifiableMap(this.locations);
    }
    
    public Map<String, SafePoint> getLocations() {
        return unmodifiableMap
    }
    
    public void setLocation(String id, int x, int y) {
        if (!locations.containsKey(id))
            throw new IllegalArgumentException("invalid vehicle name: " + id);
        locations.get(id).set(x, y);
    }
}

4.4 在现有的线程安全类中添加功能

重用能降低开发工作量、开发风险

@ThreadSafe
public class BetterVector<E> extends Vector<E> {
    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !contains(x);
        if (absent) {
            add(x);
        }
        return absent;
    }
}

扩展方法比直接将代码添加到类中更加脆弱,因为现在的同步策略实现被分布到多个单独维护的源代码中

4.4.1 客户端加锁机制

在错误的锁上进行了同步

@NotThreadSafe
public class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList());
    
    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !list.contains(x);
        if (absent) {
            list.add(x);
        }
        return absent;
    }
}

客户端加锁(破坏同步策略的封装性)

@ThreadSafe
public class ListHelper<E> {
    public list<E> list = Collections.synchronizedList(new ArrayList());
    
    public boolean putIfAbsent(E x) {
        synchronized (list) {
            boolean absent = !list.contains(x);
            if (absent) {
                list.add(x);
            }
            return absent;
        }
    }
}

4.4.2 组合

组合(Composition)

假设传递给构造函数后,客户代码不会再直接使用这个对象

@ThreadSafe
public class ImprovedList<T> implements List<T> {
    private final List<T> list;
    
    public ImprovedList(List<T> list) {
        this.list = list;
    }
    
    public synchronized boolean putIfabsent(T x) {
        boolean contains = list.contains(x);
        if (contains) {
            list.add(x);
        }
        return !contains;
    }
    
    public synchronized void clear() {
        list.clear();
    }
}

4.5 将同步策略文档化

在文档中说明客户代码需要了解的线程安全性保证,以及代码维护人员需要了解的同步策略。