1

複数のスレッドを処理する必要があるクライアントサーバーアプリケーションを作成しています。数秒ごとに生きているパケットを送信するサーバーがいくつかあります。これらのサーバーはConcurrentHashMapで維持されます。このマップには、それぞれのサーバーの最後の生きているパッケージが到着した時刻とペアになっているEndPointが含まれています。

これでスレッドができました。このスレッドは、特定の時間、生きているパケットを送信していないすべてのサーバーを「分類」する必要があります。

そんなことはできないと思いますよね?

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が許可されていない間。

これは、の典型的なシナリオのように見えます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()。次のように記載されているCHMAPIドキュメントを参照してください。

このクラスとそのビューおよびイテレータは、MapおよびIterator インターフェイスのすべてのオプションのメソッドを実装します。

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()です。エントリがない場合は、以前に表示したことがないか、最近テーブルから削除しました。これの2つの側面を調整する必要がある場合は、代わりにエントリにロック可能なレコードを導入し、そのレベルで同期を実行することをお勧めします(つまり、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(同期ラッパー)自体が更新や削除などのためにスレッドロックを提供するために使用できなくなるためです。

時間を2回チェックすると、同期が実際に削除された場合にのみ使用されることが確認されます。

于 2010-10-16T19:14:01.373 に答える