2

我有一个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,并且可能与其他操作同时进行。

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

谢谢

将要

4

2 回答 2

3

我想我遇到了这样一种情况,一个线程删除了一个我尚未迭代的元素(或者我刚刚开始迭代它),因此从循环内进行的调用失败。

我不认为这就是javadocs所说的:

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

这就是说您不必担心ConcurrentSkipListSet在您遍历列表的同时有人会从其中删除。但是,当您在迭代器中移动时,肯定会出现竞争条件。要么在您的迭代器获取它之后foo立即被删除,要么它在之前被删除并且迭代器看不到它。

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

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

于 2013-09-13T14:08:06.970 回答
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();
  }
}
于 2013-09-13T13:15:42.357 回答