使用不同的线程删除元素迭代ConcurrentSkipListSet

时间:2013-09-13 13:03:28

标签: java multithreading set java.util.concurrent

我有ConcurrentSKipListSet,我正在使用for-each循环迭代此集合中的值。 某个时刻的另一个线程是从这个集合中删除一个元素。

我想我遇到的情况是一个线程删除了一个我尚未迭代的元素(或者我刚开始迭代它),所以从循环内调用失败。

为清晰起见,有些代码:

for(Foo foo : fooSet) {
  //do stuff

  //At this point in time, another thread removes this element from the set

  //do some more stuff
  callService(foo.getId()); // Fails
}

阅读文档如果有可能,我无法解决:

  

迭代器是弱一致的,在迭代器创建时或之后的某个时刻返回反映集合状态的元素。它们不会抛出ConcurrentModificationException,并且可能与其他操作同时进行。

这是可能的,如果可以的话,处理这个问题的好方法是什么?

由于

威尔

2 个答案:

答案 0 :(得分:3)

  

我想我遇到的情况是一个线程删除了一个我尚未迭代的元素(或者我刚开始迭代它),所以从循环内调用失败

我认为这不是javadocs所说的:

  

迭代器是弱一致的,在迭代器创建时或之后的某个时刻返回反映集合状态的元素。它们不会抛出ConcurrentModificationException,并且可能与其他操作同时进行。

这就是说,您不必担心有人在您遍历列表的同时从ConcurrentSkipListSet中删除。当然,当您正在穿过迭代器时, 肯定会成为竞争条件。在你的迭代器得到它之后, 被删除,或者它被删除,迭代器没有看到它。

  

callService(foo.getId()); //这不应该“失败”

如果迭代器返回foo,则服务调用不会“失败”,除非它假设foo仍然在列表中并以某种方式检查它。最糟糕的情况是你可能会在foo上进行一些操作并使用它调用服务,即使它刚刚被另一个线程从列表中删除。

答案 1 :(得分:0)

我也遇到过这个问题,队列被不同的线程写入和读取。一种方法是标记而不是删除不再需要的元素。完成整个列表后,可以运行清理迭代器。您需要一个全局锁定才能从列表中删除元素,其余时间您的代码可以并行运行。原理图如下:

writer:
  while() {
    set.add(something);
    something.markForDelete();
  }

reader:
  while() {
    // process async
    iterator iter = set.getIterator();
    for(iter.hasNext()) {
      ... work, check isMarkedForDelete() ...
    }
    iter = set.getIterator();

    // delete, sync
    globalLock.Lock();
    for(iter.hasNext()) {
      if(something.isMarkedForDelete()) {
      set.remove(something);
    }
    globalLock.Unlock();
  }
}