简体   繁体   English

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

[英]Updating BigDecimal concurrently within ConcurrentHashMap thread safe

Is the code below thread/concurrency safe when there are multiple threads calling the totalBadRecords() method from inside other method? 当有多个线程从其他方法中调用totalBadRecords()方法时,线程/并发下面的代码是否安全? Both map objects parameters to this method are ConcurrentHashMap . 这个方法的两个map对象参数都是ConcurrentHashMap I want to ensure that each call updates the total properly. 我想确保每次通话都能正确更新总数。

If it is not safe, please explain what do I have to do to ensure thread safety. 如果不安全,请说明我必须做些什么来确保螺纹安全。

Do I need to synchronize the add/put or is there a better way? 我需要同步添加/放置还是有更好的方法?

Do i need to synchronize the get method in TestVO. 我是否需要在TestVO中同步get方法。 TestVO is simple java bean and having getter/setter method. TestVO是简单的java bean,具有getter / setter方法。

Below is my Sample code: 以下是我的示例代码:

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());

    }

That depends on how your objects are used in your whole application. 这取决于您的对象在整个应用程序中的使用方式。

If each call to totalBadRecords takes a different sourceMap and the map (and its content) is not mutated while counting, it's thread-safe: 如果每次调用totalBadRecords采用不同的sourceMap并且映射(及其内容)在计数时没有变异,那么它是线程安全的:

  • badCharges is a local variable, it can't be shared between thread, and is thus thread-safe (no need to synchronize add ) badCharges是一个局部变量,它不能在线程之间共享,因此是线程安全的(不需要同步add
  • logMap can be shared between calls to totalBadRecords : the method put of ConcurrentHashMap is already synchronized (or behaves as if it was). logMap可以呼叫之间共享,以totalBadRecords :该方法putConcurrentHashMap已经同步(或行为就好像它是)。
  • if instances of TestVO are not mutated, the value from getValue() and getInd() are always coherent with one other. 如果TestVO实例没有变异,则getValue()getInd()的值总是相互一致。
  • the sourceMap is not mutated, so you can iterate over it. sourceMap没有变异,所以你可以迭代它。

Actually, in this case, you don't need a concurrent map for sourceMap . 实际上,在这种情况下,您不需要sourceMap的并发映射。 You could even make it immutable. 你甚至可以让它变得不可变。

If the instances of TestVO and the sourceMap can change while counting, then of course you could be counting wrongly. 如果TestVOsourceMap的实例在计数时可以改变,那么当然你可能会错误地计算。

It depends on what you mean by thread-safe. 这取决于线程安全的含义。 And that boils down to what the requirements for this method are. 这可以归结为这种方法的要求

At the data structure level, the method will not corrupt any data structures, because the only data structures that could be shared with other threads are ConcurrentHashMap instances, and they safe against that kind of problem. 在数据结构级别,该方法不会破坏任何数据结构,因为可以与其他线程共享的唯一数据结构是ConcurrentHashMap实例,并且它们可以安全地抵御这种问题。

The potential thread-safety issue is that iterating a ConcurrentHashMap is not an atomic operation. 潜在的线程安全问题是迭代ConcurrentHashMap不是原子操作。 The guarantees for the iterators are such that you are not guaranteed to see all entries in the iteration if the map is updated (eg by another thread) while you are iterating. 对于迭代器的保证是这样的,如果在迭代时更新地图(例如,通过另一个线程),则不能保证看到迭代中的所有条目。 That means that the totalBadRecords method may not give an accurate count if some other thread modifies the map during the call. 这意味着如果其他线程在调用期间修改了地图,则totalBadRecords方法可能无法给出准确的计数。 Whether this is a real thread-safety issue depends on whether or not the totalBadRecords is required to give an accurate result in that circumstance. 这是否是真正的线程安全问题取决于totalBadRecords是否需要在该情况下给出准确的结果。


If you need to get an accurate count, then you have to (somehow) lock out updates to the sourceMap while making the totalBadRecords call. 如果您需要获得准确的计数,那么您必须(以某种方式)在进行totalBadRecords调用时锁定对sourceMap更新。 AFAIK, there is no way to do this using (just) the ConcurrentHashMap API, and I can't think of a way to do it that doesn't make the map a concurrency bottleneck. AFAIK,使用(仅仅) ConcurrentHashMap API无法做到这一点,我想不出一种方法,它不会使地图成为并发瓶颈。

In fact, if you need to calculate accurate counts, you have to use external locking for (at least) the counting operation, and all operations that could change the outcome of the counting. 实际上,如果需要计算准确的计数,则必须使用外部锁定(至少)计数操作,以及所有可能改变计数结果的操作。 And even that doesn't deal with the possibility that some thread may modify one of the TestVO objects while you are counting records, and cause the TestVO to change from "good" to "bad" or vice-versa. 甚至这也不能解决某些线程在计算记录时修改其中一个TestVO对象的可能性,并导致TestVO从“好”变为“坏”,反之亦然。

You could use something like the following. 您可以使用以下内容。

That would guarantee you that after a call to the totalBadRecords method, the String representing the bad charges in the logMap is accurate, you don't have lost updates . 这可以保证调用totalBadRecords方法之后,表示logMap中的不良费用的String是准确的,您没有丢失更新 Of course a phantom read can always happen, as you do not lock the sourceMap. 当然, 幻象读取总是会发生,因为你没有锁定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;
            }
        }
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM