Java並發編程閱讀筆記-Java監視器模式示例


1、前言

  書中在解釋Java監視器模式的時候使用了一個車輛追蹤器例子,根據不同的使用場景給出了不同的實現和優化。

2、監視器模式示例

  實現一個調度車輛的“車輛追蹤器”,每台車使用一個String對象標識,並且擁有一個相應的位置坐標(x,y)。由於運行在多線程的場景下,對外暴露的接口需要保證線程安全。

  需要提供的接口包括:

  1. 獲取所有車輛標識和位置
  2. 讀取某個車輛位置
  3. 更新某個車輛位置

下面給出第一種實現:

@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)和深度拷貝實現,返回的位置信息拷貝了當前的數據,包括車輛表示和對應的位置信息。這種實現方式得到的位置信息是當前的快照,這樣的數據結果是否合適取決於你的需求。

  上面這個實現使用了深度拷貝的方式,這種方式在車輛數量非常大的時候存在性能問題。那么是否可以直接返回原有的數據呢,答案是不可以,如果直接返回,這樣意味着直接發布了不支持線程安全的HashMap結構,該數據會在多個線程將共享。

  那么我們是否有其他方式解決這個問題呢。一種方案是將線程安全的能力委托給類中內部組件,而java提供了線程安全的HashMap-concurrentHashMap(HashTable、Collections.synchronizedMap()性能不及ConcurrentHashMap)

下面給出第二種實現:

@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類還是線程安全的。

  上面這個實現通過委托給支持線程安全的內部組件實現線程安全,那么是不是只要內部組件是線程安全的那這個類就是線程安全的呢,顯然不是的,如果內部組件的數據存在邏輯關系,或者存在復合操作時,線程安全需要滿足既定的邏輯關系,保證符合操作的原子性,這些都是需要額外的同步操作來完成

  在擴展原有支持線程安全類的時候,不管是通過繼承方式還是組合方式(客戶端加鎖),都需要保證擴展類中加的鎖和基類的鎖是一個鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM