一、前言java
書中在解釋Java監視器模式的時候使用了一個車輛追蹤器例子,根據不一樣的使用場景給出了不一樣的實現和優化。
安全
二、監視器模式示例數據結構
實現一個調度車輛的「車輛追蹤器」,每臺車使用一個String對象標識,而且擁有一個相應的位置座標(x,y)。因爲運行在多線程的場景下,對外暴露的接口須要保證線程安全。多線程
須要提供的接口包括:性能
下面給出第一種實現:優化
@ThreadSafe public class MonitorVehicleTracker { @GuardedBy("this") private final Map<String, MutablePoint> locations; public MonitorVehicleTracker(Map<String, MutablePoint> points) { locations = deepCopy(points); } public synchronized Map<String, MutablePoint> getLocations() { return deepCopy(locations); } public synchronized MutablePoint getLocation(String key) { MutablePoint point = locations.get(key); return point == null ? null : new MutablePoint(point); } public synchronized void setLocation(String id, int x, int y) { if (id == null) { return; } MutablePoint point = locations.get(id); if (point == null) { throw new IllegalArgumentException("No such ID: " + id); } point.setPoint(x, y); } private Map<String, MutablePoint> deepCopy(Map<String, MutablePoint> points) { if (points == null) { return Maps.newHashMap(); } Map<String, MutablePoint> result = Maps.newHashMapWithExpectedSize(points.size()); for (String key : points.keySet()) { result.put(key, new MutablePoint(points.get(key))); } return Collections.unmodifiableMap(result); } } @NotThreadSafe public class MutablePoint { private int x, y; public MutablePoint(int x, int y) { this.x = x; this.y = y; } public MutablePoint() { } public MutablePoint(MutablePoint point) { if (point == null) { throw new IllegalArgumentException("param is null"); } int[] pointArray = point.getPointArray(); x = pointArray[0]; y = pointArray[1]; } public int[] getPointArray() { int[] ret = new int[2]; ret[0] = x; ret[1] = y; return ret; } public void setPoint(int x, int y) { this.x = x; this.y = y; } }
首先須要定義一個表示位置的類MutablePoint,該類是可變的,非線程安全的。車輛追蹤器的邏輯實如今類MonitorVehicleTracker,提供了所需的三種接口邏輯。MonitorVehicleTracker是一個線程安全類,經過java內置鎖(synchronized)和深度拷貝實現,返回的位置信息拷貝了當前的數據,包括車輛表示和對應的位置信息。這種實現方式獲得的位置信息是當前的快照,這樣的數據結果是否合適取決於你的需求。this
上面這個實現使用了深度拷貝的方式,這種方式在車輛數量很是大的時候存在性能問題。那麼是否能夠直接返回原有的數據呢,答案是不能夠,若是直接返回,這樣意味着直接發佈了不支持線程安全的HashMap結構,該數據會在多個線程將共享。spa
那麼咱們是否有其餘方式解決這個問題呢。一種方案是將線程安全的能力委託給類中內部組件,而java提供了線程安全的HashMap-concurrentHashMap(HashTable、Collections.synchronizedMap()性能不及ConcurrentHashMap)線程
下面給出第二種實現:code
@ThreadSafe public class MonitorVehicleTracker { private final ConcurrentHashMap<String, ImmutablePoint> locations; private final Map<String, ImmutablePoint> unmodifiedLocations; public MonitorVehicleTracker(Map<String, ImmutablePoint> pointMap) { locations = new ConcurrentHashMap<>(pointMap); unmodifiedLocations = Collections.unmodifiableMap(locations); } public Map<String, ImmutablePoint> getLocations() { return unmodifiedLocations; } public void setLocation(String id, int x, int y) { if (StringUtils.isBlank(id)) { return; } if (locations.replace(id, new ImmutablePoint(x, y)) == null) { throw new IllegalArgumentException("No such ID: " + id); } } public ImmutablePoint getLocation(String id) { if (StringUtils.isBlank(id)) { throw new IllegalArgumentException("param is null"); } return locations.get(id); } } @Immutable @ThreadSafe public class ImmutablePoint { private final int x, y; public ImmutablePoint(int x, int y) { this.x = x; this.y = y; } public ImmutablePoint(MutablePoint point) { if (point == null) { throw new IllegalArgumentException("param is null"); } int[] pointArray = point.getPointArray(); x = pointArray[0]; y = pointArray[1]; } public int[] getPointArray() { int[] ret = new int[2]; ret[0] = x; ret[1] = y; return ret; } }
第二個實現中,MonitorVehicleTracker類的線程安全能力委託給內部組件。由於ConcurrentHashMap自己是一個線程安全的HashMap,因此無需進行深度拷貝,直接在線程間共享該數據結構便可。從上面的實現能夠看到,位置信息使用ImmutablePoint而不是MutablePoint,這是由於位置信息也會發布出去,也可能會在線程間共享,而ConcurrentHashMap只能保證自身操做的線程安全。ConcurrentHashMap的key、value都須要是線程安全的,ImmutablePoint使用不變性提供了線程安全,String能夠認爲是常量,一樣支持線程安全。與第一種實現發放不一樣的是,每一個線程拿到的位置信息視圖是一個變化的,並不是快照,若是須要快照,經過淺拷貝便可實現。
實現一個線程安全的位置信息類還能夠經過內置鎖實現,一樣,整個MonitorVehicleTracker類仍是線程安全的。
上面這個實現經過委託給支持線程安全的內部組件實現線程安全,那麼是否是隻要內部組件是線程安全的那這個類就是線程安全的呢,顯然不是的,若是內部組件的數據存在邏輯關係,或者存在複合操做時,線程安全須要知足既定的邏輯關係,保證符合操做的原子性,這些都是須要額外的同步操做來完成
在擴展原有支持線程安全類的時候,無論是經過繼承方式仍是組合方式(客戶端加鎖),都須要保證擴展類中加的鎖和基類的鎖是一個鎖。