zoukankan      html  css  js  c++  java
  • 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类还是线程安全的。

      上面这个实现通过委托给支持线程安全的内部组件实现线程安全,那么是不是只要内部组件是线程安全的那这个类就是线程安全的呢,显然不是的,如果内部组件的数据存在逻辑关系,或者存在复合操作时,线程安全需要满足既定的逻辑关系,保证符合操作的原子性,这些都是需要额外的同步操作来完成

      在扩展原有支持线程安全类的时候,不管是通过继承方式还是组合方式(客户端加锁),都需要保证扩展类中加的锁和基类的锁是一个锁。

  • 相关阅读:
    jQuery Mobile方向感应事件
    Linq-多条件查询
    linux top命令详解
    在Python中调用C++,使用SWIG
    linux下core文件调试方法
    如何设置、查看以及调试core文件
    标准C++中的string类的用法总结(转)
    实用make最佳实践
    GDB多进程调试(转)
    GDB详解
  • 原文地址:https://www.cnblogs.com/blueSkyline/p/9096725.html
Copyright © 2011-2022 走看看