设计线程安全的类:
在设计线程安全类的过程中,需要包含以下三个基本要素:
- 找出构成对象状态的所有变量
- 找出约束状态变量的不变性条件
- 建立对象状态的并发访问策略
如果不了解对象的不变性条件与后验条件,那么就不能确保线程安全性。要满足在状态变量的有效值或状态转换上的各种约束条件,就需要借助于原子性与封装性。
如果在操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作,在并发程序中一直要等到先验条件为真,然后再执行该操作。在java中,等待某个条件为真的各种内置机制(包括等待和通知机制)都与内置加锁机制紧密关联。
实例封闭:
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
例: 通过封闭机制来确保线程安全
@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); } }
由于mySet是私有的并且不会溢出,PersonSet的状态完全由它的内置锁保护,因而PersonSet是一个线程安全的类。
封闭机制更易于构造线程安全的类,因为当封闭类的状态时,在分析类的线程安全性时就无须检查整个程序。
通常我们也可以使用一个私有锁来保护状态。
public class PrivateLock{ private final Object myLock = new Object(); @GuardedBy("myLock") Widget widget; void someMethod() { synchronized(myLock) { //访问或修改Widget的状态 } } }
线程安全性的委托:
如果一个类是由多个独立且线程安全的状态变量组成,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量。
例:这里我们实现一个车辆追踪器,可以实时的返回一个车辆当前的位置,也可对某车辆的位置进行修改。
@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; public DelegatingVehicleTracker(Map<String, Point> points) { locations = new ConcurrentHashMap<String, Point>(points); } public Map<String, Point> getLocations() { return Collections.unmodifiableMap ( new HashMap<String, Point>(locations) }; } public Point getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if(location.replace(id, new Point(x, y) == null ) throw new IllegalArgumentException( "invalid vechicle name: " + id); } }
如果一个类的多个状态变量之间存在某种联系,如维持着某种不变性条件,那么就不能简单的将线程安全性委托给底层变量。
例:NumberRange类不足以保护它的不变性条件
public class NumberRange { //不变性条件:lower <= upper private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0); public void setLower(int i) { //注意:不安全的"先检查后执行" if( i > upper.get() ) { throw 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"); } public boolean isInRange(int i) { return ( i >= lower.get() && i <= upper.get( ) ); } }
如果某个类含有复合操作,例如NumberRanger,那么近依靠委托并不足以实现线程安全性。在这种情况下,这个类必须提供自己的加锁机制以保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量。
如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全地发布这个变量。
例:我们通过修改之前车辆追踪器的例子来示范如何发布一个底层变量
@ThreadSafe public class SafePoint { @GuardedBy("this") private int x, y ; private SafePoint(int []a){ this(a[0], a[1]); } public 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 ConcurrentMap<String, Point> locations; public DelegatingVehicleTracker(Map<String, Point> points) { locations = new ConcurrentHashMap<String, Point>(points); } public Map<String, Point> getLocations() { return Collections.unmodifiableMap ( new HashMap<String, Point>(locations) }; } public Point getLocation(String id) { return locations.get(id); } public void setLocation(String id, int x, int y) { if(!location.containsKey(id) ) throw new IllegalArgumentException( "invalid vechicle name: " + id); locations.get(id).set(x, y); } }
从现有的线程安全类中添加功能:
Java类库包含许多有用的“基础模块”类。有时候,某个现成的线程安全类能支持我们需要的所有操作,但更多时候,现有的类智能支持大部分的操作,此时就需要在不破坏线程安全性的情况下添加一个新的操作。
例如:假设需要一个线程安全的链表,它需要提供一个原子的"若没有则添加(Put-If-Absent)"的操作。要添加一个新的原子操作,最安全的方法是修改原始的类,但这通常无法做到。另一个方法是扩展这个类:
@ThreadSafe public class BetterVector<E> extends Vector<E> { public synchronized boolean putIfAbsent(E x) { boolean absent = !contains(x); if(absent) add(x); return absent; } }
客户端加锁机制:
第三种策略是扩展类的功能,但并不是扩展类本身,而是将扩展代码放入一个"辅助类"中。
例 - 通过客户端加锁来实现"若没有则添加"
@ThreadSafe public class ListHelper<E> { public List<E> list = Collections.synchronizedList( new ArrayList<E>( ) ); ... public boolean putIfAbsent(E x) { synchronized(list) { boolean absent = !list.contains(x); if(absent) list.add(x); return absent; } } }
对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户代码。要使用客户端加锁,你必须知道对象X使用的是哪一个锁。
组合:
当为现有的类添加一个原子操作时,有一种更好的方法:组合。下面例子中ImprovedList通过将List对象的操作委托给底层的List实例来实现List的操作,同时还添加了一个原子的putIfAbsent方法。
@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( ) ;} //...按照类似的方式委托List的其他方法 }
ImprovedList通过自身的内置锁增加了一层额外的加锁,额外的同步层可能导致轻微的性能损失,但损失还是很小的。(因为在底层List上不会再存在竞争)