【问题标题】:Remove object from list concurrently同时从列表中删除对象
【发布时间】:2015-10-12 15:34:35
【问题描述】:

我想从列表中删除一个对象,但我有一个线程一直在使用迭代器检查同一个列表。我已经有了要删除的对象,是否必须使用另一个迭代器来遍历列表,同时检查对象,然后使用迭代器将其删除,还是有更简单的方法?现在,如果我尝试使用 remove 方法从列表中删除对象,线程上的迭代器会给我一个NoSuchElementException

//Threaded loop.
Iterator<Client> playersIterator = getPlayers().iterator();
while(playersIterator.hasNext()){
    Client c = playersIterator.next(); //NoSuchElementException
    if(c.getSocket().isClosed()) {
        playersIterator.remove();
        if(getHost() == c) {
            assignNewHost();
        }
        getServer().getLobbyHandler().updateGames();
    }
}


//Use an iterator to remove?
public void removePlayerFromGame(Client client) {
    Game g = getServer().getGame(client);
    if(g != null) {
       g.getPlayers().remove(client);
    }
}

【问题讨论】:

  • 我认为最好使用synchronized 块,这样您就可以控制对集合的访问,因此基本上,removePlayerFromGame 将等待迭代器完成后再删除,或者否则,迭代器将在删除之前等待删除完成。照原样,您无法控制何时会发生什么。

标签: java multithreading java.util.concurrent


【解决方案1】:

使用synchronized控制对资源的访问...

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

synchronized {
     // your code will wait before removing the object
     g.getPlayers().remove(client);
}

【讨论】:

  • 同步在哪个显示器上?
【解决方案2】:

Iterator 只是实现遍历。如果在迭代列表时从列表中删除元素,next 方法可能会抛出 NoSuchElementException,即使 hasNext 返回 true。添加另一个迭代器不会解决问题。在浏览它们的同时,你仍然会删除它们。

您希望达到的结果是什么?你想让removePlayerFromGame 等到循环完成执行吗?在这种情况下,您需要同步访问:

Collection<Client> players = getPlayers();
synchronized(players) {
    Iterator<Client> playersIterator = getPlayers().iterator();
    // Iterator is safe from deletion
}

Collection<Client> players = getPlayers();
synchronized(players) {
    // Delete when it is safe to do so
    g.getPlayers().remove(client);
}

【讨论】:

  • 它会抛出ConcurrentModificationException,而不是NoSuchElementException
  • @AndyTurner,如果您删除一个项目并重新初始化迭代器,请跳出循环。
  • @DV88 如果不是您删除该项目,即它是其他线程怎么办?
  • @AndyTurner 仅当集合实现 modCount 时。没有提到正在使用哪种集合类型。问题中唯一可用的信息是next 抛出NoSuchElementException,但hasNext 返回true
  • 您展示的示例有一个“删除”,这就是我所引用的;一般来说,如果你正在迭代这个列表,正如@sh0rug0ru 所说,使用同步控制对它的访问。例如,调用持有列表的类进行访问。 Obv 如果您采取该步骤,您将希望确保同步代码尽可能高效。 ....我一直都有这种模式,我从各种外部类添加到队列中,然后在线程中循环并从该队列中删除以处理请求。而线程类是队列的所有者。
【解决方案3】:

您可以简单地将其标记为删除,而不是删除对象,然后在并行线程中实际删除

while(playersIterator.hasNext()){
    Client c = playersIterator.next(); //NoSuchElementException
    if (c.isMarked) {
        playersIterator.remove();
    } else if(c.getSocket().isClosed()) {
        ...

isMarked 在哪里

volatile boolean isMarked=false;

要标记要删除的对象,只需将isMarked 设置为true,无需任何同步。

【讨论】:

  • 谢谢,但我正在处理的应用程序不需要响应速度,我多次访问 ArrayList。为每种方法使用布尔值会很麻烦。同步这些方法效果很好。
猜你喜欢
  • 2021-08-03
  • 2020-04-17
  • 1970-01-01
  • 2021-02-11
  • 2014-06-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多