我正在编写一些客户端 - 服务器应用程序,我必须处理多个线程。我有一些服务器,每隔几秒发送一次活动包。这些服务器维护在ConcurrentHashMap中,其中包含EndPoints与最后一个alive-package到达相应服务器的时间。
现在我有一个线程,必须“整理”所有未在特定时间内发送alive-packets的服务器。
我想我不能那样做,可以吗?
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 );
}
有没有一种方法可以绕过它而不需要锁定整个循环(我必须在其他线程中尊重)?
答案 0 :(得分: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
。这看起来像ReadWriteLock
的典型情况,其中markActive
是“阅读”操作,removeInactive
是“写作”操作。
答案 1 :(得分:1)
我没有看到另一个线程如何在代码中更新服务器的时间。使用this.fileservers.get( server )
从地图检索服务器的时间后,另一个线程无法更改其值,因为Long对象是不可变的。是的,另一个线程可以将该服务器的新Long对象放入映射中,但这不会影响该线程,因为它已经检索了服务器的时间。
因此,我认为你的代码没有任何问题。 ConcurrentHashMap中的迭代器弱一致,这意味着它们可以容忍并发修改,因此不存在抛出ConcurrentModificationException的风险。
答案 2 :(得分:0)
(请参阅Roland's answer,其中包含了这些想法并将其充实为更完整的示例,并提供了一些很好的额外见解。)
由于它是并发哈希映射,因此您可以执行以下操作。请注意,CHM的迭代器都实现了您想要的可选方法,包括remove()
。请参阅CHM API docs,其中说明:
这个类及其视图和迭代器 实现所有可选方法
Map
和Iterator
接口。
此代码应该有效(我不知道CHM中Key
的类型):
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()
结束也可以在同一记录上同步,从而消除潜在的竞争。
答案 3 :(得分:-1)
首先使地图同步
this.fileservers = Collections.synchronizedMap(Map)
然后使用Singleton类中使用的策略
if( time > fileserverTimeout )
{
synchronized(this.fileservers)
{
if( time > fileserverTimeout )
this.fileservers.remove( server );
}
}
现在确保一旦进入synchronized块,就不会发生更新。这是因为一旦锁定在地图上,map(同步包装器)将无法自行提供线程锁定以进行更新,删除等。
检查时间两次确保仅在存在真正的删除情况时才使用同步