0

我有一组具有方法的connection对象(我无法更改的库代码)send。如果发送失败,他们会回调onClosed我在代码中实现的通用侦听器removeConnection(),这将从集合中删除连接。

回调是通用的onClosed,可以随时调用。例如,在对等方关闭连接时调用它,而不仅仅是在写入失败时调用。

但是,如果我有一些代码在我的连接上循环并发送,那么onClosed回调将尝试在迭代期间修改集合。

我当前的代码在每次迭代之前创建连接列表的副本;然而,在分析中,这已被证明是非常昂贵的。

Set<Connection> connections = new ....;

public void addConnection(Connection conn) {
    connections.add(conn);
    conn.addClosedListener(this);
}

@Override void onClosed(Connection conn) {
    connections.remove(conn);
}

void send(Message msg) {
    // how to make this so that the onClosed callback can be safely invoked, and efficient?
    for(Connection conn: connections)
        conn.send(msg);
}

如何在迭代期间有效地处理修改集合?

4

4 回答 4

3

要在没有任何异常的情况下使用并发修改迭代集合,请使用List Iterator.

http://www.mkyong.com/java/how-do-loop-iterate-a-list-in-java/ - 例子

如果您使用简单forforeach循环,您将ConcurrentModificationException在元素删除期间收到 - 请注意这一点。

另外,您可以List Iterator使用自己的覆盖并添加所需的逻辑。只需实现java.util.Iterator接口。

于 2013-05-24T08:18:43.607 回答
1

我会写一个集合包装器:

  1. 保留一组要删除的对象。如果跨底层集合的迭代遇到其中之一,则跳过它。
  2. 迭代完成后,对列表进行第二次遍历以删除所有收集的对象。

也许是这样的:

class ModifiableIterator<T> implements Iterator<T> {
  // My iterable.
  final Iterable<T> it;
  // The Iterator we are walking.
  final Iterator<T> i;
  // The removed objects.
  Set<T> removed = new HashSet<T>();
  // The next actual one to return.
  T next = null;

  public ModifiableIterator(Iterable<T> it) {
    this.it = it;
    i = it.iterator();
  }

  @Override
  public boolean hasNext() {
    while ( next == null && i.hasNext() ) {
      // Pull a new one.
      next = i.next();
      if ( removed.contains(next)) {
        // Not that one.
        next = null;
      }
    }
    if ( next == null ) {
      // Finished! Close.
      close();
    }
    return next != null;
  }

  @Override
  public T next() {
    T n = next;
    next = null;
    return n;
  }

  // Close down - remove all removed.
  public void close () {
    if ( !removed.isEmpty() ) {
      Iterator<T> i = it.iterator();

      while ( i.hasNext() ) {
        if ( removed.contains(i.next())) {
          i.remove();
        }
      }
      // Clear down.
      removed.clear();
    }
  }

  @Override
  public void remove() {
    throw new UnsupportedOperationException("Not supported.");
  }

  public void remove(T t) {
    removed.add(t);
  }

}

public void test() {
  List<String> test = new ArrayList(Arrays.asList("A","B","C","D","E"));
  ModifiableIterator i = new ModifiableIterator(test);
  i.remove("A");
  i.remove("E");
  System.out.println(test);
  while ( i.hasNext() ) {
    System.out.println(i.next());
  }
  System.out.println(test);
}

您可能需要考虑您的列表是否可以包含空值,在这种情况下,您需要对其进行一些调整。

close如果您在迭代完成之前放弃迭代,请记住迭代器。

于 2013-05-24T08:23:02.723 回答
1

ConcurrentSkipListSet可能是您想要的。

您也可以使用CopyOnWriteArraySet。当然,这仍然会进行复制,但是,只有在修改集合时才会这样做。因此,只要Connection不定期添加或删除对象,这将更有效率。

于 2013-05-24T08:36:25.460 回答
1

您还可以使用 ConcurrentHashMap。ConcurrentHashMap 是线程安全的,因此您无需复制即可进行迭代。看看这个实现.. http://www.java2s.com/Tutorial/Java/0140__Collections/Concurrentset.htm

于 2013-05-24T08:42:06.677 回答