【问题标题】:Is using a synchronised block of concurrent hash map correct?使用同步的并发哈希映射块是否正确?
【发布时间】:2018-02-05 17:59:27
【问题描述】:

根据我的理解,ConcurrentHashMap 将允许多个线程在同一个哈希映射上读取和写入(添加/删除),而不会出现并发哈希映射异常。

我有 4 个线程,每个线程都可以更新 hashmap。我不希望其他线程在当前线程更新 hashmap 时写入/更新它。

ConcurrentHashMap<String, Integer> playerLoginCounterHashMap = new ConcurrentHashMap<>();

    ExecutorService executorService = Executors.newFixedThreadPool(4);

    for (int i = 0; i < 4; i++) {

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                synchronized (playerLoginCounterHashMap) {
                    if (playerLoginCounterHashMap.get("testPlayer") == null) {
                        playerLoginCounterHashMap.put("testPlayer", 1);
                    } else {
                        playerLoginCounterHashMap.put("testPlayer", playerLoginCounterHashMap.get("testPlayer").intValue() + 1);
                    }
                }
            }
        });
    }

这是正确的做法吗?如果没有同步块,我会得到不正确的值。

【问题讨论】:

  • 那个代码会为你生成异常?
  • 您需要使用并发感知 API,例如 putIfAbsentcomputeIfAbsent 以允许并发。否则,如果您的整个更新过程需要是原子的,那么唯一的解决方案是锁定并防止任何其他线程在您更新时使用映射,此时 ConcurrentHashMap 不会为您增加任何价值。跨度>
  • @DanielPryden putIfAbsent 似乎不像文档中的 computeIfAbsent 那样是原子的
  • @forum.test17 putIfAbsent 仅以原子方式处理“put”操作,这是真的。因此,您需要处理多个线程竞相计算相同的值。 compute 函数,因为它需要一个 lambda,所以可以为您处理所有这些,但在 Java 的 8 之前版本中,您没有 lambdas 或 compute,因此您需要使用像 @987654327 这样的工具自己做@.

标签: java concurrency concurrenthashmap


【解决方案1】:

是的,它是正确的(假设这是唯一更新地图的地方),但它效率低下,因为它是同步的,而不是依赖于地图固有的非阻塞并发。

您应该改用compute()

playerLoginCounterHashMap.compute(
    "testPlayer",
    (key, value) -> value == null ? 1 : value + 1);

merge():

playerLoginCounterHashMap.merge(
    "testPlayer",
    1,
    Integer::sum);

【讨论】:

    【解决方案2】:

    请注意,在存储每个用户长计数器的简单情况下,使用Google Guava AtomicLongMap 可能有意义:

    final AtomicLongMap<String> loginCounterByPlayerName = AtomicLongMap.create();
    final ExecutorService executorService = Executors.newFixedThreadPool(4);
    for (int i = 0; i < 4; i++) {
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                loginCounterByPlayerName.addAndGet("testPlayer", 1);
            }
        });
    }
    

    唯一不同的是计数器从 0 开始。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-20
      • 1970-01-01
      • 2023-03-21
      相关资源
      最近更新 更多