【问题标题】:How to implement a concurrent Set that maintains insertion order如何实现保持插入顺序的并发Set
【发布时间】:2011-09-21 07:17:01
【问题描述】:


我需要一个 Set 实现,它可以让我保持插入顺序并且仍然可以同时修改(如不抛出 ConcurrentModificationException)。

我尝试将 ConcurrentSkipListSet 与我自己的比较器一起使用 - 示例代码:

public static void main(String[] str){
        ConcurrentSkipListSet set  = new ConcurrentSkipListSet(new Comparator() {

            public int compare(Object o1, Object o2) {
                if(o1.equals(o2)){
                    return 0;
                }
                return -1;
            }
        });
        set.add("d");
        set.add("b");
        set.add("a");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        set.add("c");
        set.add("b");

        System.out.println(set);
        set.remove("b");
        System.out.println(set);
    }

但是这个比较器似乎是一个#fail,因为集合打印:
[b, c, a, b, d] 。如果b在那里两次,它就没有设置。
我还有其他选择吗?

【问题讨论】:

标签: java collections set concurrent-collections


【解决方案1】:

您定义了一个不遵守total order property 的比较器。对于两个对象,要么一个比另一个小,要么另一个比第一个小。

在您的情况下,如果对象不相等,则每个对象都小于另一个对象。

由于您在实例化 ConcurrentSkipListSet 时没有任何类型参数来说明集合中元素的类型,因此您将难以定义比较器,除非您使用强制转换。但是如果你创建一个new ConcurrentSkipListSet<String>,定义比较器会更容易,因为你会知道你的对象是字符串。

【讨论】:

  • 我正在尝试将自然排序的属性(即字符串的升序)改造成插入顺序。那么问题来了,这样的改造是否可行?
  • 如果您想保留插入顺序,还可以使用其他并发数据结构——例如并发队列。对于并发跳过列表,如果您保留一个使用 CAS 增加的全局计数器,并在将元素插入列表之前将其值写入元素,则可能是可能的。那么问题是你失去了线性化属性 - 有人可能会在“标记”对象和插入之间的时间内将另一个对象插入到列表中。我想说的是,如果不修改 conc-skip-lists 的内部结构,这是非常困难或不可能的。
  • 如上面评论中所述,您可以跟踪创建顺序,但不一定是插入顺序。也许第一个是你需要的......
【解决方案2】:

您可以定义一个比较器来保持字符串的插入顺序并使用它,它不会很漂亮,但由于总是为每个新元素调用比较器,您需要做的就是这样:

public void testInsertionOrderSkipListSet() {
  Comparator<String> insertionOrderComparator = new Comparator<String>() {

    private final ConcurrentHashMap<String, Integer> order = new ConcurrentHashMap<String, Integer>();
    @Override
    public int compare(String o1, String o2) {
      if (!order.contains(o2)) //only happens on second insert
        order.put(o2, 0);
      if (order.containsKey(o1))
        return order.get(o1).compareTo(order.get(o2));
      order.put(o1, order.size());
      return 1;
    }
  };
  ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<String>(insertionOrderComparator);

  set.add("a");
  set.add("c");
  set.add("e");
  set.add("b");
  set.add("d");
  set.add("c");
  assertArrayEquals(new String[] { "a", "c", "e", "b", "d"}, set.toArray(new String[]{}));
}

嘿,我说它不漂亮...

【讨论】:

  • 你是对的,如果它是改装的,那就是改装。我修改(并在此处发布)此解决方案以使删除也保持一致。
【解决方案3】:

我几乎使用了@Asaf 的解决方案,但我对其进行了一些改进,以使其也适用于删除操作:

class ConcurrentInsertionOrderSet extends ConcurrentSkipListSet{
        Map<Object, Integer> orderMap;
        final AtomicInteger increment = new AtomicInteger();
        public ConcurrentInsertionOrderSet(final Map<Object, Integer> orderMap) {
            super(new Comparator<Object>() {      
                public int compare(Object o1, Object o2) {
                    return (orderMap.get(o1).compareTo(orderMap.get(o2)));
                }
            });
            this.orderMap = orderMap;
        }

        @Override
        public boolean add(Object o) {
            if (!orderMap.containsKey(o)) 
                orderMap.put(o, increment.incrementAndGet());
            return super.add(o);
        }
        @Override
        public boolean remove(Object o) {
            boolean b = super.remove(o);
            if(b)
                orderMap.remove(o);
            return b;
        }
    }

测试:

public static void main(String[] str){
        ConcurrentSkipListSet set  = new ConcurrentInsertionOrderSet(new ConcurrentHashMap());
        set.add("d");
        set.add("b");
        set.add("a");
        set.add("c");
        set.add("b");
        set.add("c");
        set.add("g");
        System.out.println(set);
        set.remove("b");
        System.out.println(set);
        set.remove("c");
        set.add("c");
        System.out.println(set);
    }

输出很好且一致:
[d, b, a, c, g]
[d, a, c, g]
[d, a, g, c]

但我猜@axel22 对比赛条件的担忧仍然存在。

【讨论】:

    猜你喜欢
    • 2010-10-15
    • 1970-01-01
    • 2013-10-21
    • 1970-01-01
    • 2021-08-19
    • 2010-12-09
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多