1

我正在编写一些必须处理多个线程的客户端-服务器-应用程序。我有一些服务器,每隔几秒钟发送一次活动数据包。这些服务器在 ConcurrentHashMap 中维护,其中包含它们的 EndPoints 与相应服务器的最后一个活动包到达的时间配对。

现在我有一个线程,它必须“整理”所有在特定时间内没有发送活动数据包的服务器。

我想我不能就那样做,可以吗?

for( IPEndPoint server : this.fileservers.keySet() )
{
    Long time = this.fileservers.get( server );

    //If server's time is updated here, I got a problem

    if( time > fileserverTimeout )
        this.fileservers.remove( server );
}

有没有一种方法可以在不为整个循环获取锁的情况下解决这个问题(然后我也必须在其他线程中尊重)?

4

4 回答 4

3

这里可能没有问题,具体取决于您在地图中存储的确切内容。您的代码对我来说看起来有点奇怪,因为您似乎保存了“服务器未激活的持续时间”。

我记录该数据的第一个想法是存储“服务器处于活动状态的最新时间戳”。然后您的代码将如下所示:

package so3950354;

import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class ServerManager {

  private final ConcurrentMap<Server, Long> lastActive = new ConcurrentHashMap<Server, Long>();

  /** May be overridden by a special method for testing. */
  protected long now() {
    return System.currentTimeMillis();
  }

  public void markActive(Server server) {
    lastActive.put(server, Long.valueOf(now()));
  }

  public void removeInactive(long timeoutMillis) {
    final long now = now();

    Iterator<Map.Entry<Server, Long>> it = lastActive.entrySet().iterator();
    while (it.hasNext()) {
      final Map.Entry<Server, Long> entry = it.next();
      final long backThen = entry.getValue().longValue();
      /*
       * Even if some other code updates the timestamp of this server now,
       * the server had timed out at some point in time, so it may be
       * removed. It's bad luck, but impossible to avoid.
       */
      if (now - backThen >= timeoutMillis) {
        it.remove();
      }
    }
  }

  static class Server {

  }
}

如果您真的想避免在调用markActive期间没有任何代码调用removeInactive,则无法绕过显式锁定。你可能想要的是:

  • markActive允许并发调用。
  • 期间markActive不允许打电话removeInactive
  • 期间removeInactive不允许打电话markActive

这看起来像是 a 的典型场景ReadWriteLock,其中markActive“读取”操作removeInactive是“写入”操作。

于 2010-10-16T23:16:42.790 回答
1

我看不到另一个线程如何在您的代码中更新服务器的时间。一旦您使用 从地图中检索到服务器的时间this.fileservers.get( server ),另一个线程将无法更改其值,因为 Long 对象是不可变的。是的,另一个线程可以将该服务器的新 Long 对象放入地图中,但这不会影响该线程,因为它已经检索了服务器的时间。

因此,就目前而言,我看不出您的代码有任何问题。ConcurrentHashMap 中的迭代器是弱一致的,这意味着它们可以容忍并发修改,因此也不存在引发 ConcurrentModificationException 的风险。

于 2010-10-16T23:10:43.887 回答
0

(请参阅Roland 的回答,它采用了这里的想法并将它们充实到一个更完整的示例中,并提供了一些很棒的额外见解。)

由于它是一个并发哈希映射,您可以执行以下操作。请注意,CHM 的迭代器都实现了remove()您想要的可选方法,包括 。请参阅CHM API 文档,其中指出:

此类及其视图和迭代器实现了MapIterator 接口的所有可选方法。

此代码应该可以工作(我不知道Key您的 CHM 中的类型):

ConcurrentHashMap<K,Long> fileservers = ...;

for(Iterator<Map.Entry<K,Long>> fsIter = fileservers.entrySet().iterator(); fileservers.hasNext(); )
{
    Map.Entry<K,Long> thisEntry = fsIter.next();
    Long time = thisEntry.getValue();

    if( time > fileserverTimeout )
        fsIter.remove( server );
}

但请注意,其他地方可能存在竞争条件......您需要确保访问地图的其他代码位可以应对这种自发删除 - 即,可能无论您触摸到哪里,fileservers.put()您都需要一些涉及的逻辑fileservers.putIfAbsent(). 与使用相比,此解决方案不太可能造成瓶颈synchronized,但也需要更多思考。

你写的“如果服务器的时间在这里更新,我遇到了问题”正是putIfAbsent()出现的地方。如果该条目不存在,要么你以前没有见过它,要么你刚刚从表中删除了它。如果这两个方面需要协调,那么您可能希望为条目引入一个可锁定的记录,并在该级别进行同步(即同步在记录上remove(),而不是在整个表上) . 然后put()事情的结束也可以在同一记录上同步,消除潜在的比赛。

于 2010-10-16T19:31:30.117 回答
-1

首先使地图同步

this.fileservers = Collections.synchronizedMap(Map)

然后使用单例类中使用的策略

if( time > fileserverTimeout )
    {
        synchronized(this.fileservers)
        {
             if( time > fileserverTimeout )
                  this.fileservers.remove( server );
        }
    }

现在这可以确保一旦您进入同步块,就不会发生更新。之所以如此,是因为一旦获取了 map 上的锁,map(synchronized wrapper) 本身将无法为其提供线程锁以进行更新、删除等操作。

两次检查时间确保只有在真正的删除情况下才使用同步

于 2010-10-16T19:14:01.373 回答