【问题标题】:is ConcurrentHashMap.put() always Thread-Safe? if so, then why it is not working correctly?ConcurrentHashMap.put() 总是线程安全的吗?如果是这样,那么为什么它不能正常工作?
【发布时间】:2021-08-22 20:21:02
【问题描述】:

我编写了一个类 (userRepository),其中包含一个名为 init() 的方法。 init 方法初始化 ConcurrentHashMap,当我在多个线程中调用此方法时(例如在三个线程中,每个线程中 n 次),我希望映射的大小等于 nx3。但事实并非如此! 另一方面,当我在方法的签名上使用 ReentrantLock 或 synchronized 关键字时,它工作得很好。我的意思是地图的大小等于 nx3。 请检查以下示例:

public class UserRepository implements CustomRepository<User, Integer> {
    private final Map<Integer, User> userMap = new ConcurrentHashMap<>();
    private int index = 0;

    public void init() {
        userMap.put(index, new User("User_" + index).setId(index));
        index++;
    }

    public List<User> findAll() {
        List<User> userList = new ArrayList<>();

        for (Integer id : userMap.keySet())
            userList.add(userMap.get(id));

        return userList;
    }
}
public class ChallengeApplication {
    static ExecutorService ex = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        UserRepository userRepository = new UserRepository();

        int a = 5000000;
        long start = System.currentTimeMillis();
        ex.submit(() -> {
            for (int j = 0; j < a; j++)
                userRepository.init();
            long time = System.currentTimeMillis() - start;
            System.out.println(Thread.currentThread() + " finished in " + time);
        });

        ex.submit(() -> {
            for (int j = 0; j < a; j++)
                userRepository.init();
            long time = System.currentTimeMillis() - start;
            System.out.println(Thread.currentThread() + " finished in " + time);
        });

        for (int j = 0; j < a; j++)
            userRepository.init();
        long time = System.currentTimeMillis() - start;
        System.out.println(Thread.currentThread() + " finished in " + time);

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Size of map: " + userRepository.findAll().size());
    }
}

这是控制台输出:

Thread[pool-1-thread-2,5,main] finished in 3832

Thread[main,5,main] finished in 3938

Thread[pool-1-thread-1,5,main] finished in 3996

Size of map: 14347920```

你可以看到 n=5000000 并且有 3 个线程,所以我希望池的大小等于 5000000*3= 15000000 但它是 13991739!

我想知道这个冲突的原因是什么!?

请注意,当我输入 synchronize 关键字或使用 ReentrantLock 时,它可以正常工作

【问题讨论】:

  • ConcurrentHashMap.put() 本身是线程安全的(如果多个线程同时调用它不会搞砸),但它当然不能使您自己的周围代码线程安全(例如,递增 @ 987654325@等)。
  • 实际上,我尝试了 AtomicInteger 而不是 int,但没有任何改变并且仍然以错误的方式工作,但你是对的,整个方法不是线程安全的,那么解决方案是什么?如果我们应该使用 synchronized 关键字或 ReentrantLock,我们可以使用 HashMap 代替 ConcurrentHashMap
  • 两个独立的“线程安全变量”一起使用不会构成线程安全的方法/程序。
  • @GaëlJ 谢谢,将 ConcurrentHashMap 依赖于另一个不是线程安全的变量是我的错误

标签: java data-structures collections concurrency concurrenthashmap


【解决方案1】:

问题不在于哈希映射本身,而在于索引。您使用的 ++ 不是原子命令,因此不是线程安全的。当您在函数上使用同步字时,它也会同步索引并解决问题。

【讨论】:

  • 您的意思是,如果我使用 AtomicInteger 而不是 int,它应该可以正常工作,对吧?你是对的 index++ 不是线程安全的,但是我将它更改为 AtomicInteger 并且仍然以错误的方式工作
  • 不是一个人,问题出在两个线程之间的索引上。您需要每次为每个线程锁定它,并且只有在创建用户并推进索引之后,才释放锁定并且没有锁定您可以将用户添加到地图中,它不需要是并发的
  • 所以你的意思是,正如我在问题中提到的,我必须通过添加同步或锁定/释放机制以某种方式锁定它。谢谢,将 ConcurrentHashMap 依赖于另一个不是线程安全的变量是我的错误。
猜你喜欢
  • 1970-01-01
  • 2016-06-17
  • 2015-04-23
  • 2018-11-10
  • 1970-01-01
  • 1970-01-01
  • 2016-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多