设计线程安全的类

实例限制

当一个对象被另一个对象封装时,所有访问被被封装对象的代码路径就是全部可知的,这相比于让对象可被整个系统访问来说,更容易对代码路径进行分析。将数据封装在对象内部,把对数据的访问限制在对象的方法上,更易确保线程在访问数据时总能获得正确的锁

被限制对象一定不能溢出到它的期望可用的范围之外。可以把对象限制在本地变量、类的实例(比如私有类的成员)或线程(比如对象在线程内部从一个方法传递到另一个方法,不过前提是该对象不被跨线程共享),下面的例子未对Person的线程安全性作任何假设,但如果它是可变的,那么可能还需要额外同步

@ThreadSage
public class PersonSet
{
@GuardedBy("this")
private final Set mySet=new HashSet();
public synchronized void addPerson(Person p)
{
mySet.add(p);
}
public synchronized boolean containsPerson(Person p)
{
return mySet.contains(p);
}
}

监视器

使用私有锁对象,而不是对象的内部锁(或任何其他可公共访问的锁)

public class PrivateLock
{
private final Object myLock=new Object();
@GuardedBy("myLock") Widget widget;
void somenMethod()
{
synchronized(myLock)
{
//访问或修改widget的状态
}
}
}

范例:机动车追踪器

package com.henrysun.javaSE.bfbc;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.annotation.GuardedBy;
/**
* 线程安全类,JAVA监视器模式
* 范例:机动车追踪器
* 一个用于调度出租车,警车,货运卡车等机动车的“机动车追踪器”,每一辆机动车都有一个String标识
* 并有一个与之对应的位置(X,Y),视图线程和多个更新线程会并发的访问数据模型
* @author Sam Flynn
*
*/
public class MonitorVehicleTracker {
@GuardedBy("this")
private final Map locations;
public MonitorVehicleTracker(Map locations)
{
this.locations=deepCopy(locations);
}
public synchronized Map getLocations() {
return deepCopy(locations);
}
public synchronized MutablePoint getLocation(String id) {
MutablePoint loc=locations.get(id);
return loc==null?null:new MutablePoint(loc);
}
public synchronized void setLocation(String id,int x,int y)
{
MutablePoint loc=locations.get(id);
if(loc==null)
{
throw new IllegalArgumentException("No such ID:"+id);
}
loc.x=x;
loc.y=y;
}
private static Map deepCopy(Map m)
{
Map result=new HashMap();
for(String id:m.keySet())
{
result.put(id, new MutablePoint(m.get(id)));
}
return Collections.unmodifiableMap(result);
}
}
/**
* 可变point
* @author Sam Flynn
*
*/
class MutablePoint
{
public int x,y;
public MutablePoint(){x=0;y=0;}
public MutablePoint(MutablePoint p)
{
this.x=p.x;
this.y=p.y;
}
}

先复制可变的数据,再返回给用户,这种实现方式只能简单的维护线程安全。如果locations集合不是非常大的话,这种做法通常不会造成性能问题。每次调用getLocations前先复制全部数据还会产生另一种副作用-即使真实的location已经改变,返回的容器的内容仍然不会改变

委托线程安全

我们向一个无状态的类(假设A)中添加一个线程安全的类型(假设B)的属性,所得组合对象仍然是线程安全的,因为A的状态就是线程安全类B的状态,并且A没有对B的状态增加额外的有效性的约束,可以说A将它的线程安全性委托给了B