在讲以前,咱们先看一个Java监视器模式示例,这个示例是用于调度车辆的车辆追踪器,首先使用监视器模式来构建车辆追踪器,而后再尝试放宽某些封装性需求同时又保持线程安全性。每台车都由一个String对象来标识,而且拥有一个相应的位置坐标。在MonitorVehicleTracker类中封装了全部车辆的标识和位置,且这个类将被一个读取线程和多个更新线程所共享,因此这个类必定要是线程安全的。java
import java.util.*; /** 基于监视器模式的线程安全的车辆追踪 MutablePoint没有被发布出去,发布出去的只是一个数据与MutablePoint相同的新对象,因此修改MutablePoint的值只能经过setLocation来实现 */ public class MonitorVehicleTracker{ private final Map<String,MutablePoint> locations; /** 构造时采用的是深拷贝,若是不采用深拷贝,那么在构造完后,locations就被发布出去了,这样里面的数据就可能被不安全的修改 好比 Map<String,MutablePoint> locations=... MonitorVehicleTracker m=new MonitorVehicleTracker(locations); ... 构造完后就能够在这之后的代码往locations中添加和删除车辆,这是不安全的 */ public MonitorVehicleTracker(Map<String,MutablePoint> locations){ this.locations=deepCopy(locations); } public synchronized Map<String,MutablePoint> getLocations(){ return deepCopy(locations); } /** 防止MutablePoint被发布出去,返回一个数据相同的新的对象, */ public synchronized MutablePoint getLocation(String id){ MutablePoint point=locations.get(id); return point==null?null:new MutablePoint(point); } public synchronized void setLocation(String id,int x,int y){ MutablePoint point=locations.get(id); if(point==null){ throw new IllegalArgumentException("No Such Id: "+id); } point.x=x; point.y=y; } private static Map<String,MutablePoint> deepCopy(Map<String,MutablePoint> m){ Map<String,MutablePoint> result=new HashMap<String,MutablePoint>(); for(String id:m.keySet()){ result.put(id,new MutablePoint(m.get(id)));//防止locations中的MutablePoint被发布出去,返回一个数据相同的新对象 } return Collections.unmodifiableMap(result);//返回一个不能被修改的map } } /** 非线程安全 */ class MutablePoint{ public int x,y; public MutablePoint(){ x=0;y=0; } public MutablePoint(MutablePoint p){ this.x=p.x; this.y=p.y; } }
在这里,虽然MutablePoint不是线程安全的,但因为MonitorVehicleTracker是线程安全的,其共有方法都使用了内置锁进行同步,而且它所包含的Map对象和可变的MutablePoint都不曾发布。当须要返回车辆的位置时,经过MutablePoint拷贝函数或者deepCopy方法复制正确的值,从而生成一个键值对都与原Map对象都相同的新的Map对象。 但这也会产生一个性能上的问题,当车辆较多时,复制数据会占用较多的时间。并且,当复制完成后,若是车辆的位置发生了变化,并不会即便反应到读取线程中去,这是好是坏就依状况而定了。为了解决这些问题,接下来咱们就使用其它的方式来实现这个线程安全类安全
###委托函数
所谓委托,就是使用一个本来就线程安全的类来管理类的某个或某些状态,在上面的例子中,线程安全主要体如今locations状态上,因此,咱们如今将locations委托给线程安全的ConcurrentHashMap。性能
import java.util.*; import java.util.concurrent.*; /** 不使用deepCopy 基于委托的车辆追踪器 虽然Point被发布出去了,可是unmodifiableMap添加或删除替换里面的车辆,而Point又是不可变的,因此跟没发布出去同样 只能经过MonitorVehicleTracker1的setLocation方法来修改Point */ public class MonitorVehicleTracker1{ private final ConcurrentMap<String,Point> locations; private final Map<String,Point> unmodifiableMap; public MonitorVehicleTracker1(Map<String,Point> map){ locations=new ConcurrentHashMap<String,Point>(map); unmodifiableMap=Collections.unmodifiableMap(locations); } /** MonitorVehicleTracker类中返回的是快照,而在这里返回的是不可修改但却实时的车辆位置视图,这就意味着若是返回后车辆的位置发生了变化,对于视图线程来讲,是可见的。 */ public Map<String,Point> getLocations(){ return unmodifiableMap; //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(locations.replace(id,new Point(x,y))==null){//若是车辆不存在 throw new IllegalArgumentException("invalid vehicle name:"+id); } } } /** 一个不可变的Point类 Point必须是不可变的,由于getLocations会发布一个含有可变状态的Point引用 */ class Point{ .... }
咱们能够看到,当使用线程安全的类去管理locations时,对于locations的get和replace都将是线程安全的,因此不须要像第一个例子同样,对getLocation和setLocation进行同步,而对于getLocations方法来讲,返回的是一个不可修改的Map,因此也不用担忧Map中的对象被更新或者是删除。可是,尽管如此,对于Map中的Point对象,咱们仍是能够修改的里面的属性值的(Point对象被发布出去了),并且getLocation方法也发布了一个Point对象,因此,这也是不安全的,所以,咱们要么将Point对象设置成不可变的,要么不发布Point,在这里,咱们选择前者。this
/** 一个不可变的Point类 Point必须是不可变的,由于getLocations会发布一个含有可变状态的Point引用 */ class Point{ public final int x,y; public Point(int x,int y){ this.x=x; this.y=y; } }
在调用getLocations时,返回给读线程的是一个Collections.unmodifiableMap(locations)对象,因此若是写线程使用了setLocation来更新车辆的信息,读线程都能及时的看到。线程
对于Point来讲,它要么是不可变的,要么不会被发布出去,可是,当咱们须要发布一个可变的Point时,咱们就须要建立一个线程安全的可变Point,这样,即便是被发布出去,也保证了线程安全性。code
public class SafePoint{ 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; } }
public class MonitorVehicleTracker2{ private final Map<String,SafePoint> locations; private final Map<String,SafePoint> unmodifiableMap; public MonitorVehicleTracker2(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 SafePoint getLocation(String id){ return locations.get(id); } 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);//set是线程安全的 } }
固然,上面只是个很简单的例子,对于复杂的共享类来讲,多个状态之间可能会存在不变性,这时,若是只是使用线程安全类来委托的话,并不能解决线程安全问题。像下面的例子对象
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()){ 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"); } upper.set(i); } public boolean isInRange(int i){ return (i>=lower.get()&&i<=upper.get()); } }
在上面的例子中,存在lower<upper的不变性约束,尽管setXXX方法使用了先检查后执行
对这个不变性进行了维持,但并无使用足够的加锁机制来保证这些操做的原子性,试想若是两个方法同时被两个线程分别调用,那么就可能破坏这种不变性。因此仅靠委托是不够的,还必须对这些操做进行加锁同步。get
###组合 当咱们要为一个线程安全的类添加一个新的操做时,咱们能够选择多种方式,像修改原始的类,虽然这彷佛并不怎么理想(由于你不必定能获取到原始类的源码),也能够扩展这个类。好比咱们要给Vector添加一个若没有则添加
的操做。同步
public class MyVector<E> extends Vector<E>{ public synchronized boolean putIfAbsent(E x){ boolean absent=!contains(x); if(absent){ add(x); } return absent; } }
咱们还能够在使用时进行加锁
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; } } }
固然,使用这种方式咱们必须使list操做加锁时和putIfAbsent操做加锁时使用的是同一个锁,不然就是非线程安全的。
public class ListHelper<E>{ public List<E> list=Collections.synchronizedList(new ArrayList<E>()); ... public synchronized boolean putIfAbsent(E x){ boolean absent=!list.contains(x); if(absent){ list.add(x); } return absent; } }
很明显,putIfAbsent操做的加锁对象是ListHelper对象,而list的操做的加锁对象是list,加锁对象不一样,就不能保证线程的安全性。
当为现有的类添加一个操做时,为了保证原有类的线程安全,咱们还可使用更好的方式,那就是组合。咱们能够将List对象的操做委托给底层的List实例来实现List的操做,同时还添加一个原子的putIfAbsent方法,这样,List实例就不能直接被访问,而是要经过封装类来实现。
public class MyList<E> implements List<T>{ private final List<T> list; public MyList<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 add(T x){...} // ... 使用内置锁实现 list的其它方法 }
因此,无论list是否是线程安全的,MyList也都提供了一致的加锁机制来实现线程安全性。但这样的实现,会产生一些性能上的损失,但实现更加健壮。