保持数据结构视图的一致性

时间:2014-02-25 18:24:29

标签: java data-structures synchronization

我有MapOrder个,可由许多不同的线程访问。我想控制访问,所以请考虑以下简单的数据结构+包装器。

public interface OrderContainer {
    boolean contains(String orderId);
    Order get(String orderId);
    Order register(Order value);
    Order remove(String orderId);
    Collection<Order> getOrders();
}

public class SimpleOrderContainer implements OrderContainer {
    private Map<String, Order> orders = new ConcurrentHashMap<>();
    private Collection<Order> ordersView = Collections.unmodifiableCollection(orders.values());

    @Override
    public boolean contains(String orderId) {
        return orders.containsKey(orderId);
    }

    @Override
    public Order get(String orderId) {
        return orders.get(orderId);
    }

    @Override
    public Order register(Order value) {
        return orders.put(value.getId(), value);
    }

    @Override
    public Order remove(String orderId) {
        return orders.remove(orderId);
    }

    @Override
    public Collection<Order> getOrders() {
        return ordersView;
    }
}

很简单。现在,Order还有另一种方法getType。我想decorate我的班级能够按类型访问Order,但我不希望每次调用此方法时都要迭代整个地图;我想保留一个包含此信息的视图。

问题:

  1. 以线程安全的方式保持两个视图彼此一致
  2. 尝试执行#1可能会导致过度同步,从而影响性能
  3. 我无法确保没有人保留对原始OrderContainer的引用,这会导致typeView不同步。
  4. 这是我第一次尝试装饰课程。这种尝试几乎肯定会过度同步:

    public class TypeOrderContainer implements OrderContainer {
        private OrderContainer backing;
    
        private Map<String, Map<String, Order>> typeView = new ConcurrentHashMap<>();
    
        TypeOrderContainer(OrderContainer backing) {
            this.backing = backing;
        }
    
        public boolean contains(String orderId) {
            return backing.contains(orderId);
        }
    
        public Order get(String orderId) {
            return backing.get(orderId);
        }
    
        public synchronized Order register(Order value) {
            String type = value.getType();
    
            Map<String, Order> innerMap = getInnerMap(type);
            innerMap.put(value.getId(), value);
    
            return backing.register(value);
        }
    
        private Map<String, Order> getInnerMap(String type) {
            if(!typeView.containsKey(type)) {
                return addInnerMap(type);
            } else {
                return typeView.get(type);
            }
        }
    
        private Map<String, Order> addInnerMap(String type) {
            Map<String, Order> innerMap = new ConcurrentHashMap<>();
    
            typeView.put(type, innerMap);
    
            return innerMap;
        }
    
        public synchronized Order remove(String orderId) {
            Order order = backing.remove(orderId);
    
            if(order == null) return null;
    
            String type = order.getType();
            Map<String, Order> innerMap = getInnerMap(type);
            if(innerMap == null) {
                // I suspect this is not the best error handling logic
                throw new IllegalStateException("Somehow the inner map is out of sync!!");
            } else {
                innerMap.remove(order.getId());
                // Could do this if you want, likely not necessary in my use case
                // if (innerMap.isEmpty()) typeView.removeInnerMap(); 
            }
    
            return order;
        }
    
        public Collection<Order> getOrders() {
            return backing.getOrders();
        }
    
        public Map<String, Order> getOrdersByType(String type) {
            return Collections.unmodifiableMap(getInnerMap(type));
        }
    }
    

    是否有更好的方法可以保持数据视图的一致性并保持线程安全?

2 个答案:

答案 0 :(得分:1)

在你的情况下我不相信#3是可能的。基本上你想要的是当OrderContainer中发生插入/删除(不使用你的TypeOrderContainer装饰器)时,你想要TypeOrderContainer(特别是你的typeView)知道插入/删除同时(看看那里已经存在向后依赖?)。如果这不是您要求的,请忽略此答案的其余部分。

在您的情况下,您对引用的OrderContainer有一个完全独立的视图,其中正在构建和维护String typeCollection<Order> typedOrders之间的关系。通过OrderContainer中声明的方法,我们最多可以假设有Collection<Order> allOrders。现在让我们假设有typeOrders1typeOrders2,它们一起形成allOrders。如果我将订单插入allOrdersallOrders的集合如何知道将typeOrder放入新订单?答案是allOrders除非理解类型的关系,否则// This assumes your maps are ConcurrentHashMaps public Order register(Order value) { // No synchronized here since CHM does it for you Map<String, Order> innerMap = getInnerMap(type); innerMap.put(value.getId(), value); return backing.register(value); } private Map<String, Order> getInnerMap(String type) { if (!typeView.containsKey(type)) { synchronized(typeView) { if (!typeView.containsKey(type)) { // make sure no one else snuck in after you "checked" typeView.put(type, new ConcurrentHashMap<>()); } } } return typeView.get(type); } public Order remove(String orderId) { // No synchronized here since CHM does it for you Order order = backing.remove(orderId); if(order == null) return null; String type = order.getType(); Map<String, Order> innerMap = getInnerMap(type); if(innerMap == null) { // I suspect this is not the best error handling logic throw new IllegalStateException("Somehow the inner map is out of sync!!"); } else { innerMap.remove(order.getId()); // Could do this if you want, likely not necessary in my use case // if (innerMap.isEmpty()) typeView.removeInnerMap(); } return order; } public Collection<Order> getOrders() { return backing.getOrders(); } public Map<String, Order> getOrdersByType(String type) { return Collections.unmodifiableMap(getInnerMap(type)); } 将不会...订单,如果确实如此,它会破坏这个装饰器的重点。最终,除非子集理解了它所寻找的范围,否则不可能让子集理解对原始集的更新,并且你的allOrders没有“类型”范围。如果您有兴趣,java的TreeSet.subSet有一个非常类似的问题,其中任何超出指定子集范围的添加都不会出现。

另一方面,我相信您的解决方案已经完全适合您的目的。我要做的一个优化是减少同步。您需要同步typeView的唯一一次是在插入全新类型时:

OrderContainer backing

此外,一个typical decorator pattern示例将显示这通常是可能的,因为装饰器类只是环绕备份对象(在您的情况下为{{1}}),并且重写方法应用添加到支持的对象。但是,由于您基本上将OrderContainer转换(/拆分)到另一个视图,这对您来说并不适用。

答案 1 :(得分:0)

如果您不想为每个调用筛选原始地图,为什么不将另一个类型的地图保留到订单中,每次更改原始地图时都会更新相应的类型地图?进一步思考,我当然会质疑同步两个集合然后迭代一个集合并“动态”构建副本是否更复杂。