2

我有一个网络应用程序。tomcat在多个线程上运行并为Servlet调用提供服务。

我有一个User类、一个Account类和一个 1AccountContext` 类。

Accounts可以有多个Users.

每个. AccountContext_Account

当用户通过 servlet 进行登录调用时:如果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

恕我直言,除非您保证所有线程都具有相同的帐户实例并且没有办法让两个帐户对象代表相同的“帐户” ,否则它不是线程安全的,请考虑这种情况:两个线程各有一个帐户对象,代表相同帐户,他们都调用 getAccountContext(),第一个线程在 if(accountContext == null) 行之后但在开始初始化之前被暂停,然后第二个线程到达相同的时间,验证 accountContext 为 null 并继续创建 AccountContext ,然后再次给第一个线程 CPU 时间,因为第一个线程已经“验证”了 accountContext 为空,它将继续创建另一个实例。

尝试使用地图 itselt (ACCOUNT_CONTEXT_MAP) 而不是每个 Account 对象进行同步。

如果您不想在地图上同步,因为它会导致其他线程等待创建昂贵的 AccountContext,然后试试这个:

  • 创建一个新类:AccountContextBuilder:一个创建成本不高的类,它构建了一个昂贵的AccountContext。这个类将包含一个构建器方法来创建一个AccountContext或返回一个以前创建的。
  • 使您的地图包含AccountContextBuilder而不是AccountContext的实例。
  • 在地图上同步(无论如何你需要让它同步),这一次它不会惩罚其他线程,因为你将创建一个“便宜”的构建器对象。
  • 最后,线程使用这个构建器来访问AccountContext,这样其他线程就不会因为其他 AccountContexts 而受到惩罚。
于 2013-06-30T23:55:56.740 回答
0

正如@morgano 指出的那样,这仅适用account于所有相同帐户的相同实例。此外,Map必须是线程安全的——这通常意味着使用 aConcurrentHashMap或类似的。如果你的地图不是线程安全的,那么get第一行的不是线程安全的——很多不好的事情都可能出错。

你可以做的一件事是给你的锁上条纹。创建一个包含 N 个对象的数组(字面意思Object很好)。当您需要synchronized块的锁时,从 获取它account.hashCode() % locksArray.length,然后在该对象上进行同步。这意味着您将能够AccountContext并行创建许多 s,只要它们Account的 s 不同hashCode() % N。平均而言,这应该会给您带来良好的性能;显然它假设Account有一个合适的hashCode()覆盖。

private final Object[] locks = createLocks();

private static Object[] createLocks() {
    Object[] locks = new Object[20]; // or whatever
    for (int i = 0; i < locks.length; ++i) {
        locks[i] = new Object();
    }
}

if (accountContext == null) {
    Object lock = locks[account % locks.length];
    synchronized (lock) {
        ...
    }
}

最后,这是一件小事,但在同步块中,你有:

if(ACCOUNT_CONTEXT_MAP.get(account) == null) {
    ...

我会做:

accountContext = ACCOUNT_CONTEXT_MAP.get(account);
if (accountContext == null) {
    ...

那么你就不需要这个else块了。

于 2013-07-01T00:37:35.203 回答