0

当有多个线程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());

    }
4

3 回答 3

1

这取决于线程安全的含义。这归结为这种方法的要求是什么。

在数据结构级别,该方法不会破坏任何数据结构,因为可以与其他线程共享的唯一数据结构是ConcurrentHashMap实例,并且它们可以安全地避免此类问题。

潜在的线程安全问题是迭代 aConcurrentHashMap不是原子操作。迭代器的保证是这样的,如果在迭代时更新映射(例如,由另一个线程),则不能保证在迭代中看到所有条目。这意味着totalBadRecords如果其他线程在调用期间修改了映射,该方法可能无法给出准确的计数。这是否是一个真正的线程安全问题取决于在这种情况下是否totalBadRecords需要给出准确的结果。


sourceMap如果您需要获得准确的计数,那么您必须(以某种方式)在拨打电话时锁定更新totalBadRecords。AFAIK,没有办法使用(仅)ConcurrentHashMapAPI 来做到这一点,而且我想不出一种不会使地图成为并发瓶颈的方法。

实际上,如果您需要计算准确的计数,则必须(至少)对计数操作以及所有可能改变计数结果的操作使用外部锁定。即使这样也不能解决某些线程可能会TestVO在您计算记录时修改其中一个对象并导致TestVO从“好”变为“坏”或反之亦然的可能性。

于 2013-09-09T12:16:50.170 回答
1

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

如果每次调用都totalBadRecords采用不同的方式sourceMap,并且地图(及其内容)在计数时没有发生变化,那么它是线程安全的:

  • badCharges是一个局部变量,它不能在线程之间共享,因此是线程安全的(不需要同步add
  • logMap可以在对 的调用之间共享totalBadRecords: 的方法put已经ConcurrentHashMap同步(或表现得好像是一样)。
  • 如果 的 实例TestVO未发生突变,则getValue()和的值getInd()始终相互一致。
  • thesourceMap没有突变,因此您可以对其进行迭代。

实际上,在这种情况下,您不需要为sourceMap. 你甚至可以让它不可变。

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

于 2013-09-09T12:04:56.760 回答
0

您可以使用以下内容。

这将保证您调用该totalBadRecords方法后,表示 logMap 中不良费用的字符串是准确的,您不会丢失更新。当然,幻读总是会发生,因为您没有锁定 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;
            }
        }
    }
于 2013-09-09T12:52:21.423 回答