首页 > 解决方案 > 映射中的线程安全写入

问题描述

我在下面提供了一段代码:

Map<String, BigDecimal> salesMap = new HashMap<>();

orderItems.parallelStream().forEach(orderItem -> {

            synchronized (this) {

                int itemId = orderItem.getItemId();
                Item item = settingsClient.getItemByItemId(itemId);

                String revenueCenterName = itemIdAndRevenueCenterNameMap.get(itemId);
                updateSalesMap(salesMap, "Gross Sales: " + revenueCenterName, orderItem.getNetSales().toPlainString());

            }
});


private void updateSalesMap(Map<String,BigDecimal> salesMap, String key, String amount) {

        BigDecimal bd = getSalesAmount(salesMap, key);
        int scale = 2;

        if (StringUtils.isBlank(amount)) {
            amount = "0.00";
        }

        BigDecimal addMe = BigDecimal.valueOf(Double.valueOf(amount)).setScale(scale, RoundingMode.HALF_UP);
        salesMap.put(key, bd.add(addMe));
    }

该代码工作正常,但如果我不使用该synchronized块,它将结束地图中的变化数据。据我所知,流是线程安全的,所以我很好奇发生了什么。我尝试使用 ConcurrentHashMap 但似乎没有任何改变。

我的想法是地图数据不写在 RAM 中,读/写是在线程缓存中完成的,因此我们最终会得到各种数据。

这是正确的吗?如果是这样,我将使用volatile关键字,然后使用同步块。

注意:只是发现我不能volatile在方法中声明变量。

标签: javaconcurrency

解决方案


据我所知,流是线程安全的,所以我很好奇发生了什么。

他们是。只要您只对流本身进行操作。问题是您尝试同时操纵其他变量(在这种情况下为映射)。流的思想是对每个元素的操作是完全独立的——检查函数编程的思想。

我尝试使用ConcurrentHashMap,但似乎没有任何改变。

问题来自您的方法。一般的想法是原子操作ConcurrentHashMap是线程安全的。但是,如果同时执行两个线程安全操作,它就不是原子和线程安全的。您需要自己同步它或提出其他解决方案。

updateSalesMap()方法中,您首先从地图中获取值,进行一些计算,然后更新值。这一系列操作不是原子的——执行它们ConcurrentHashMap不会有太大变化。

在这种情况下实现并发的一种可能方法是利用CuncurrentHashMap.compute() Javadocs


推荐阅读