我有一个网络应用程序。在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;
}
答案 0 :(得分:1)
恕我直言,它不是线程安全,除非您保证所有线程都具有相同的帐户实例,并且无法让两个帐户对象代表相同的“帐户”,请考虑以下情况:线程每个都有一个Account对象,代表同一个账号,它们都调用getAccountContext(),第一个线程在行if(accountContext == null)之后立即挂起但在开始初始化之前,然后第二个线程得到同样的石灰,验证accountContext为null并继续创建AccountContext,然后第一个线程再次给出CPU时间,因为第一个线程已经“验证”accountContext为null,它将继续创建另一个实例。
尝试使用map itselt(ACCOUNT_CONTEXT_MAP)而不是每个Account对象进行同步。
如果您不想在地图上进行同步,因为这会导致其他线程等待创建昂贵的AccountContext,请尝试以下操作:
答案 1 :(得分: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;
答案 2 :(得分:0)
正如@morgano指出的那样,只有当account
是所有同等帐户的同一个实例时,这才有效。此外,Map
必须是线程安全的 - 这通常意味着使用ConcurrentHashMap
或类似的。如果你的地图不是线程安全的,那么第一行的get
就不是线程安全的 - 很多坏事都可能出错。
你可以做的一件事是锁定你的锁。创建一个N个对象的数组(字面意思Object
很好)。当您需要锁定synchronized
块时,从account.hashCode() % locksArray.length
获取它,然后在该对象上进行同步。这意味着只要他们的AccountContext
具有不同的Account
,您就可以并行创建多个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
阻止。