使用带有多个线程 Java 的 ConcurrentHashMap 的不同结果

Posted

技术标签:

【中文标题】使用带有多个线程 Java 的 ConcurrentHashMap 的不同结果【英文标题】:Different results using ConcurrentHashMap with multiple Threads Java 【发布时间】:2021-12-19 02:50:41 【问题描述】:

我将 main 中的字符串列表拆分为 2 个不同的线程来映射里面的单词。 每次执行此代码时,我都会得到不同的映射结果。 要么我有一个很大的逻辑缺陷,要么我缺少关于线程和并发集合的一些东西。 谁能理解为什么会发生这种情况? 列表中添加了 8 个“a”和 6 个“b”。 附言如果我只使用一个线程,这不会发生!

编辑 1 将 map.put() 更改为 map.merge(word, 1, Integer::sum),仍然不起作用 编辑 2 以下解决方案我没有使用 if/else,仅合并,它按预期工作。

 public class MyThread extends Thread 
     private List<String> list;
     private final ConcurrentHashMap<String, Integer> map;


    public MyThread(ConcurrentHashMap<String, Integer> map, List<String> list) 
        this.map = map;
        this.list = list;
    

    @Override
    public void run() 
        for (String word : list)
           map.merge(word, 1, Integer::sum);
       
   

    public ConcurrentHashMap<String, Integer> getMap() 
       return map;
   



public static void main(String[] args) throws InterruptedException 
    ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
    List<String> list = new ArrayList<>();

    list.add("a");list.add("a");list.add("a");list.add("a");list.add("b");list.add("b");list.add("b");
    list.add("a");list.add("a");list.add("a");list.add("a");list.add("b");list.add("b");list.add("b");

    MyThread[] ts = new MyThread[2];
    int start = 0;
    int end = list.size()/2;

    for (int i = 0; i < 2; i++)
        ts[i] = new MyThread(map,new ArrayList<>(list.subList(start, end)));
        ts[i].start();

        start = end;
        end = list.size();
    

    for (int i = 0; i < 2; i++)
        ts[i].join();
    

    for(String word : map.keySet())
        System.out.println("Key = " + word + ". Value = " + map.get(word));
    

【问题讨论】:

"map.put(word, map.getOrDefault(word, 0) + 1);" - 这些操作不是原子执行的。我建议改用ConcurrentHashMap&lt;String, AtomicInteger&gt;。 This question by Makkador 有类似的根本原因,尽管我认为它不是重复的。 或者map.merge(word, 1, Integer::sum); @Turing85 使用 ConcurrentHashMap 并使用 put(StringKey, incremented AtomicInteger or 1) 更新地图仍然返回不同的结果 我们的想法是不使用put(....) 覆盖现有值,而是通过get(...)tin 和ConcurrentHashMap 中的AtomicInteger 修改现有值并在其上调用incrementAndGet()。但老实说:看到@shmosel 的评论,这似乎更清晰,更容易理解。 @Turing85,好吧,脑子坏了。实际上没有想到这一点。会及时通知您! 【参考方案1】:

第一件事 - 你应该使用 map.containsKey(word) 而不是 map.contains(word)

关于一些运行后的不同结果。当您调用 containsKey()merge() 方法时,没有原子代码。它们都原子地分开,但不是原子地在一起。 解决方案只是更改您的代码并仅调用原子的merge()。你根本不需要if-else 部分。

    @Override
    public void run() 
        for (String word : list) 
            map.merge(word, 1, Integer::sum);
        
    

【讨论】:

以上是关于使用带有多个线程 Java 的 ConcurrentHashMap 的不同结果的主要内容,如果未能解决你的问题,请参考以下文章

如何使用Lock取代synchronized

并发库应用之四 & 线程锁Lock应用

Java多线程:用三个线程控制循环输出10次ABC

线程池使用实例

201771010118 马昕璐 《面向对象设计 java》第十七周实验总结

如果我同时(从多个线程)写入java.util.HashMap会发生什么最糟糕的事情?