Java 如何实现维护插入顺序的并发集

Java 如何实现维护插入顺序的并发集,java,collections,set,concurrent-collections,Java,Collections,Set,Concurrent Collections,我需要一个集合实现,它可以让我保持插入顺序,并且仍然可以进行震荡式修改(如不抛出ConcurrentModificationException)我尝试将ConcurrentSkipListSet与我自己的比较器一起使用-示例代码: public static void main(String[] str){ ConcurrentSkipListSet set = new ConcurrentSkipListSet(new Comparator() {


我需要一个集合实现,它可以让我保持插入顺序,并且仍然可以进行震荡式修改(如不抛出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);
    }
但是这个比较器似乎失败了,因为集合打印:
[b,c,a,b,d]。如果b在里面两次,那就没有定数了

我还应该考虑其他的替代方案吗?

您已经定义了一个比较器,它不符合标准。对于两个对象,要么一个比另一个小,要么另一个比第一个小

在您的例子中,如果对象不相等,则每个对象都比另一个小


由于实例化
ConcurrentSkipListSet
时没有任何类型参数说明集合中元素的类型,因此除非使用强制转换,否则在定义比较器时会遇到问题。但是,如果您创建一个新的ConcurrentSkipListSet,那么定义比较器就更容易了,因为您知道您的对象是字符串。

您可以定义一个比较器来保持字符串的插入顺序,并使用它,这并不漂亮,但由于每个新元素都会调用comparator,所以您需要做的事情如下:

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[]{}));
}
public void testInsertionOrderSkipListSet(){
Comparator insertionOrderComparator=新比较器(){
私有最终ConcurrentHashMap顺序=新ConcurrentHashMap();
@凌驾
公共整数比较(字符串o1、字符串o2){
如果(!order.contains(o2))//仅在第二次插入时发生
顺序。放置(o2,0);
if(订单号containsKey(o1))
返回订单.get(o1).compareTo(订单.get(o2));
order.put(o1,order.size());
返回1;
}
};
ConcurrentSkipListSet=新的ConcurrentSkipListSet(insertionOrderComparator);
集合。添加(“a”);
设置。添加(“c”);
设置。添加(“e”);
设置。添加(“b”);
设置。添加(“d”);
设置。添加(“c”);
AssertArrayQuals(新字符串[]{“a”、“c”、“e”、“b”、“d”},set.toArray(新字符串[]{}));
}

嘿,我说它不太好…

我基本上使用了@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;
        }
    }
输出良好且一致:
[d,b,a,c,g]
[d,a,c,g]
[d,a,g,c]


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

答案是这个家伙在问什么。我正在尝试将自然顺序(即字符串的升序)的属性改为插入顺序。因此,可能会出现这样一个问题:如果您希望保留插入顺序,那么可以使用其他并发数据结构,例如并发队列。对于并发跳过列表,如果在将元素插入列表之前保留一个随CAS增加的全局计数器,并将其值写入元素,则可能会发生这种情况。然后,问题是您将失去线性化特性—在“标记”对象和插入对象之间的时间内,可能有人将另一个对象插入列表。我要说的是,如果不修改conc skip list的内部结构,这是非常困难或不可能的。如上面的评论所述,您可以跟踪创建顺序,但不一定是插入顺序。也许第一个是你需要的……你是对的,如果它是复古版,那么它就是复古版。我对这个解决方案进行了修改(并在这里发布),使其在删除时也保持一致。
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);
    }