在ConcurrentHashMap线程安全中并发更新BigDecimal

时间:2013-09-09 11:34:04

标签: java concurrency

当有多个线程从其他方法中调用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());

    }

3 个答案:

答案 0 :(得分:1)

这取决于您的对象在整个应用程序中的使用方式。

如果对totalBadRecords的每次调用都采用不同的sourceMap并且地图(及其内容)在计数时没有变异,则它是线程安全的:

  • badCharges是一个局部变量,它不能在线程之间共享,因此是线程安全的(不需要同步add
  • logMap可以在对totalBadRecords的调用之间共享:put的{​​{1}}方法已经同步(或者表现得像是一样)。
  • 如果ConcurrentHashMap的实例未发生变异,则TestVOgetValue()的值始终保持一致。
  • getInd()未发生变异,因此您可以对其进行迭代。

实际上,在这种情况下,您不需要sourceMap的并发地图。你甚至可以让它变得不可变。

如果sourceMapTestVO的实例在计数时可能会发生变化,那么当然您可能会错误地计算。

答案 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;
            }
        }
    }