2

私はウェブアプリケーションを持っています。tomcat呼び出しを処理する複数のスレッドで実行されていServletます。

Userクラス、クラスAccount、および 1AccountContext` クラスがあります。

Accounts複数持つことができますUsers

ごとにのインスタンスを 1 つだけAccountContextメモリに保持する必要がありますAccount

ユーザーがサーブレット経由でログイン呼び出しを行った場合: がAccountContext存在する場合は、それを返します。それ以外の場合は、初期化します。

以下は、コンテキストを初期化するために書いたコードです。このコードは、スレッドセーフでありながら、私が望むことを行うように見えますか?

ACCOUNT_CONTEXT_MAPですConcurrentHashMap

public static AccountContext getAccountContext(Account account) {
    AccountContext accountContext = ACCOUNT_CONTEXT_MAP.get(account);
    if(accountContext == null){
        synchronized(account){
            if(ACCOUNT_CONTEXT_MAP.get(account) == null)
                accountContext = new AccountContext(account);
                //Creating the AccountContext is expensive, 
                //i'd like it if it was only done once.
                ACCOUNT_CONTEXT_MAP.put(account,accountContext);        
            }else{
                accountContext = ACCOUNT_CONTEXT_MAP.get(account);
            }
        }
    }
    return accountContext;
}
4

3 に答える 3

1

ConcurrentHashMap.putIfAbsentこのような状況向けに特別に設計された同期の代わりに、アトミック メソッドを使用します。使用方法は次のとおりです。

AccountContext accountContext = ACCOUNT_CONTEXT_MAP.get(account);
if (accountContext == null){
    accountContext = new AccountContext(account);
    AccountContext accountContextOld = ACCOUNT_CONTEXT_MAP.putIfAbsent(account, accountContext);        
    if (accountContextOld != null) {
         accountContext = accountContextOld;
    }
}
return accountContext;
于 2013-07-01T03:20:10.173 に答える
1

IMHOすべてのスレッドが account の同じインスタンスを持ち、同じ "account" を表す 2 つの Account オブジェクトを持つ方法がないことを保証しない限り、スレッド セーフではありません。このシナリオを検討してください。2 つのスレッドがそれぞれ Account オブジェクトを持ち、同じものを表します両方とも getAccountContext() を呼び出します。最初のスレッドは行 if(accountContext == null) の直後に中断されますが、初期化を開始する前に、2 番目のスレッドがこれと同じタイミングになり、accountContext が null であることを確認し、AccountContext の作成に進みますこの最初のスレッドはすでに accountContext が null であることを「検証」しているため、別のインスタンスの作成に進みます。

各 Account オブジェクトではなく、マップ自体 (ACCOUNT_CONTEXT_MAP) を使用して同期してみてください。

高価な AccountContext が作成されるのを他のスレッドが待機する原因となるため、マップ上で同期したくない場合は、これを試してください。

  • 新しいクラスを作成します: AccountContextBuilder : 高価なAccountContextを構築する安価な作成クラス。このクラスには、AccountContextを作成するか、以前に作成したものを返すビルダー メソッドが含まれます。
  • AccountContextではなくAccountContextBuilderのインスタンスを含むようにマップを作成します。
  • マップ上で同期します (とにかく同期する必要があります)。今回は、「安価な」ビルダー オブジェクトを作成するため、他のスレッドにペナルティを課すことはありません。
  • 最後に、スレッドはこのビルダーを使用してAccountContextへのアクセスを取得します。このようにして、他の AccountContext が原因で他のスレッドがペナルティを受けることはありません。
于 2013-06-30T23:55:56.740 に答える