当有多个线程从其他方法中调用totalBadRecords()
方法时,线程/并发下面的代码是否安全?这个方法的两个映射对象参数都是ConcurrentHashMap
。我想确保每次通话都能正确更新总数。
如果不安全,请说明我必须做些什么来确保线程安全。
我是否需要同步添加/放置或有更好的方法吗?
我是否需要在TestVO中同步get方法。 TestVO是简单的java bean并且具有getter / setter方法。
以下是我的示例代码:
public void totalBadRecords(final Map<Integer, TestVO> sourceMap,
final Map<String, String> logMap) {
BigDecimal badCharges = new BigDecimal(0);
boolean badRecordsFound = false;
for (Entry<Integer, TestVO> e : sourceMap.entrySet()) {
if ("Y".equals(e.getValue().getInd()))
badCharges = badCharges.add(e.getValue()
.getAmount());
badRecordsFound = true;
}
if (badRecordsFound)
logMap.put("badRecordsFound:", badCharges.toPlainString());
}
答案 0 :(得分:1)
这取决于您的对象在整个应用程序中的使用方式。
如果对totalBadRecords
的每次调用都采用不同的sourceMap
并且地图(及其内容)在计数时没有变异,则它是线程安全的:
badCharges
是一个局部变量,它不能在线程之间共享,因此是线程安全的(不需要同步add
)logMap
可以在对totalBadRecords
的调用之间共享:put
的{{1}}方法已经同步(或者表现得像是一样)。ConcurrentHashMap
的实例未发生变异,则TestVO
和getValue()
的值始终保持一致。 getInd()
未发生变异,因此您可以对其进行迭代。 实际上,在这种情况下,您不需要sourceMap
的并发地图。你甚至可以让它变得不可变。
如果sourceMap
和TestVO
的实例在计数时可能会发生变化,那么当然您可能会错误地计算。
答案 1 :(得分:1)
这取决于线程安全的含义。这归结为此方法的要求。
在数据结构级别,该方法不会破坏任何数据结构,因为可以与其他线程共享的唯一数据结构是ConcurrentHashMap
个实例,并且它们可以安全地抵御这类问题。
潜在的线程安全问题是迭代ConcurrentHashMap
不是原子操作。对于迭代器的保证是这样的,如果在迭代时更新地图(例如,通过另一个线程),则不能保证看到迭代中的所有条目。这意味着如果某个其他线程在调用期间修改了地图,totalBadRecords
方法可能无法给出准确的计数。这是否是真正的线程安全问题取决于totalBadRecords
是否需要在此情况下提供准确的结果。
如果您需要获得准确的计数,那么在进行sourceMap
调用时,您必须(以某种方式)锁定totalBadRecords
的更新。 AFAIK,使用(仅仅)ConcurrentHashMap
API无法做到这一点,我无法想到一种不会使地图成为并发瓶颈的方法。
事实上,如果你需要计算准确的计数,你必须使用外部锁定(至少)计数操作,以及所有可能改变计数结果的操作。甚至这也不能解决某些线程在计算记录时修改TestVO
个对象之一的可能性,并导致TestVO
从“好”变为“坏”或副-versa。
答案 2 :(得分:0)
您可以使用以下内容。
这样可以保证在调用totalBadRecords
方法之后,表示logMap中的不良费用的字符串是准确的,您没有丢失的更新< / em>的。当然,总是会发生幻像读取,因为你没有锁定sourceMap。
private static final String BAD_RECORDS_KEY = "badRecordsFound:";
public void totalBadRecords(final ConcurrentMap<Integer, TestVO> sourceMap,
final ConcurrentMap<String, String> logMap) {
while (true) {
// get the old value that is going to be replaced.
String oldValue = logMap.get(BAD_RECORDS_KEY);
// calculate new value
BigDecimal badCharges = BigDecimal.ZERO;
for (TestVO e : sourceMap.values()) {
if ("Y".equals(e.getInd()))
badCharges = badCharges.add(e.getAmount());
}
final String newValue = badCharges.toPlainString();
// insert into map if there was no mapping before
if (oldValue == null) {
oldValue = logMap.putIfAbsent(BAD_RECORDS_KEY, newValue);
if (oldValue == null) {
oldValue = newValue;
}
}
// replace the entry in the map
if (logMap.replace(BAD_RECORDS_KEY, oldValue, newValue)) {
// update succeeded -> there where no updates to the logMap while calculating the bad charges.
break;
}
}
}