7

人々がリソースを要求するWebアプリケーションがあります。このリソースは、効率化のために同期ハッシュマップを使用してキャッシュされます。ここでの問題は、2つの異なるリクエストが同じキャッシュされていないリソースに対して同時に発生する場合です。リソースを取得する操作は大量のメモリを消費するため、同じリソースに対して複数回呼び出すことは避けたいと思います。

次のスニペットに潜在的な問題があるかどうか誰かに教えてもらえますか?前もって感謝します。

private Map<String, Resource> resources = Collections.synchronizedMap(new HashMap<String, Resource>());

public void request(String name) {

  Resource resource = resources.get(name);

  if (resource == null) {
    synchronized(this) {
      if (resources.get(name) == null) {
        resource = veryCostlyOperation(name); // This should only be invoked once per resource...
        resources.put(resource);
      } else {
        resource = resources.get(name);
      }
    }
  }

  ...

}
4

4 に答える 4

6

veryCostlyOperation()考えられる問題の1つは、ブロック内で実行することによって不要な競合が発生しsynchronized、多くのスレッドが(独立した)リソースを同時に取得できないことです。Future<Resource>これは、マップの値として使用することで解決できます。

Map<String, Future<Resource>> map = new ConcurrentHashMap<String, Future<Resource>>();    
...
Future<Resource> r = map.get(name);
if (r == null) {
    FutureTask task = null;
    synchronized (lock) {
        r = map.get(name);
        if (r == null) {
            task = new FutureTask(new Callable<Resource>() {
                public Resource call() {
                    return veryCostlyOperation(name);
                }
            });
            r = task;
            map.put(name, r);
        }
    }
    if (task != null) task.run(); // Retrieve the resource
}

return r.get(); // Wait while other thread is retrieving the resource if necessary
于 2011-03-30T15:24:09.977 に答える
2

私が見る唯一の潜在的な問題は、に同期することthisです。同じクラスの他のコードもに同期するthis場合、それらのブロックの1つだけが一度に実行されます。たぶん、これを行うものは他にありません、そしてそれは問題ありません。しかし、私は常に次のプログラマーが何をするのか心配しています。(または私がこのコードを忘れた3か月後の私自身)

一般的な同期オブジェクトを作成してから、それに同期することをお勧めします。

プライベート最終オブジェクトresourceCreationSynchObject=new Object();

それから

同期(this.resourceCreationSynchObject){
  ..。
}

そうでなければ、これはあなたが求めていることを正確に行います。veryCostlyOperation並行して呼び出すことができないことを保証します。

synchronizedまた、ブロック内でもう一度リソースを再取得することは素晴らしい考えです。これは必要であり、外部での最初の呼び出しにより、リソースがすでに使用可能になっているときに同期しないようにします。しかし、それを3度目に呼ぶ理由はありません。synchronizedブロック内で最初に設定resourceresources.get(name)、その変数がnullであるかどうかを確認します。これにより、句get内で再度呼び出す必要がなくなります。else

于 2011-03-30T15:06:59.713 に答える
1

実際に必要以上に同期していることを除いて、コードは問題ないように見えます。

  • ConcurrentHashMap同期の代わりにを使用するHashMapと、ロックせずにgetメソッドを複数回呼び出すことができます。

  • this代わりに同期するresources必要はおそらくありませんが、コードの残りの部分によって異なります。

于 2011-03-30T15:07:38.333 に答える
0

コードは、veryCostlyOperation(name)を複数回呼び出す可能性があります。問題は、マップを検索した後に同期されていないステップがあることです。

public void request(String name) {
    Resource resource = resources.get(name);
    if (resource == null) {
        synchronized(this) {
            //...
        }
    }
    //...
}

マップからのget()はマップによって同期されますが、結果のnullのチェックは何によっても保護されません。複数のスレッドが同じ「名前」を要求してこれに入ると、実際にcostlyOperationを終了してリソースをリソースマップに配置するまで、すべてのスレッドにresources.get()からのnull結果が表示されます。

より単純で機能しますが、スケーラブルではないアプローチは、法線マップを使用して、要求メソッド全体を同期させることです。実際に問題が発生しない限り、私は単純なアプローチを選択します。

スケーラビリティを高めるために、同期後(this)にマップを再度チェックしてコードを修正し、上記のケースをキャッチできます。同期(this)では1つのスレッドのみがcostlyOperationを実行できるため、それでも最高のスケーラビリティは得られませんが、多くの実際のケースでは、異なるリソースへの同時リクエストを許可しながら、同じリソースに対する複数の実行を防止したいだけです。その場合、要求されているリソースで同期するための機能が必要です。非常に基本的な例:

private static class ResourceEntry {
     public Resource resource;
}

private Map<String, ResourceEntry> resources = new HashMap<String, ResourceEntry>();

public Resource request(String name) {
    ResourceEntry entry;
    synchronized (resources) {
        entry = resources.get(name);
        if (entry == null) {
            // if no entry exists, allocate one and add it to map
            entry = new ResourceEntry();
            resources.put(name, entry);
        }
    }
    // at this point we have a ResourceEntry, but it *may* be no loaded yet
    synchronized (entry) {
        Resource resource = entry.resource;
        if (resource == null) {
            // must create the resource
            resource = costlyOperation(name);
            entry.resource = resource;
        }
        return resource;
    }
}

これは大まかなスケッチにすぎません。基本的に、ResourceEntryの同期ルックアップを作成し、次にResourceEntryで同期して、特定のリソースが1回だけビルドされるようにします。

于 2011-03-30T21:56:13.670 に答える