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



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

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。我想修饰我的类,使其能够按类型访问Orders,但我不想每次调用此方法时都必须遍历整个映射;我想保留一个包含这些信息的视图。

问题:

  1. 以线程安全的方式保持两个视图的一致性
  2. 在我尝试执行#1时可能出现过度同步,影响性能
  3. 我不能确保没有人保留对原始OrderContainer的引用,这会使我的typeView不同步

这是我第一次尝试装饰类。这种尝试几乎肯定会过度同步:

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));
}
}

有没有更好的方法来保持我的数据视图的一致性并保持线程安全?

我认为#3在您的情况下是不可能的。本质上,您想要的是,当在OrderContainer中发生插入/删除时(不使用TypeOrderContainer装饰器),您希望TypeOrderContainer(特别是您的typeView)同时了解插入/删除(看看那里是如何存在向后依赖关系的?)。如果这不是你所问的,忽略这个答案的其余部分。

在您的案例中,您有一个完全独立的参考OrderContainer视图,其中正在构建和维护String typeCollection<Order> typedOrders之间的关系。通过在OrderContainer中声明的方法,我们最多可以假设存在Collection<Order> allOrders。现在让我们假设有typeOrders1typeOrders2,它们一起形成allOrders。如果我在allOrders中插入一个订单,那么allOrders的集合如何知道将新订单放入哪个typeOrder?答案是allOrders不会,除非它理解类型-->Orders的关系,如果它理解了,它就会破坏这个装饰器的要点。最终,不可能让子集了解原始集合的更新,除非子集了解它要查找的范围,并且您的allOrders没有"类型"范围。如果您感兴趣的话,java的TreeSet.subSet也有一个非常类似的问题,在这个问题中,任何超出指定子集范围的添加都不会出现。

另一方面,我相信您的解决方案已经为您的目的设计好了。我要做的一个优化是减少同步。唯一需要同步typeView的时间是插入一个全新的类型:

// 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));
}

此外,一个典型的decorator模式示例将表明这通常是可能的,因为decorator类只是包装在支持的对象(在您的案例中为OrderContainer backing)周围,并且重写的方法将添加应用于支持的对象。然而,由于您基本上是在将OrderContainer转换(/分解)到另一个视图,因此这对您来说并不太可行。

如果你不想为每次调用筛选原始映射,为什么不保留另一个类型映射到订单,每次更改原始映射时,都会相应地同步更新类型映射?进一步思考,我肯定会质疑同步两个集合是否比迭代一个集合并"动态"构建副本更复杂。

最新更新