在講以前,咱們先看一個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也都提供了一致的加鎖機制來實現線程安全性。但這樣的實現,會產生一些性能上的損失,但實現更加健壯。