【问题标题】:ConcurrentHashMap synchronization issueConcurrentHashMap 同步问题
【发布时间】:2017-07-29 11:23:25
【问题描述】:

我使用 ConcurrentHashMapTreeSet 作为值。此哈希映射由多个线程共享。

当一个线程接收到客户端对某个键的请求时,它会检查该键是否存在于 hashmap 中,如果存在,它将新请求作为 MyClass 对象添加到相应的TreeSet 中。否则创建一个新的TreeSet 并使用该键插入HashMap

代码sn-p:

public static ConcurrentHashMap<String, TreeSet<MyClass>> messageMap =
        new ConcurrentHashMap<String, TreeSet<MyClass>>();

TreeSet<MyClass> treeSM = new TreeSet<MyClass>();

if (messageMap.containsKey(key)) {
    treeSM = messageMap.get(key);
}

treeSM.add(sm);
messageMap.put(key, treeSM);

如果两个线程同时收到对相同键的请求,则映射包含不一致的值。两个线程都将messageMap.containsKey(key) 的结果设为false,并将新构建的TreeSet 对象作为键值。所以最终那个键的 TreeSet 只有一个 MyClass 对象而不是两个。

【问题讨论】:

    标签: multithreading synchronization java-7 concurrenthashmap


    【解决方案1】:

    如果您被 Java 7 卡住了,您想要做这样的事情来正确利用 ConcurrentHashMap

    TreeSet<MyClass> treeSM = messageMap.get(key);     // 1
    if (treeSM == null) {                              // 2
        TreeSet<MyClass> newTree = new TreeSet<>();    // 3
        treeSM = messageMap.putIfAbsent(key, newTree); // 4
        if (treeSM == null) {                          // 5
            treeSM = newTree;                          // 6
        }
    }
    synchronized (treeSM) {                            // 7
        treeSM.add(sm);                                // 8
    }
    

    遍历上面的代码行:

    1. 不要打扰containsKey(),只需致电get()。如果密钥不在您的地图中,这将返回 null大部分时间会返回非null...
    2. 但如果它 null...
    3. 创建一个新的TreeSet,并将其放入Map
    4. 尝试通过调用putIfAbsent() 将新的TreeSet 放入MapputIfAbsent() 的语义是这样的,如果密钥在 Map 中,它将只返回已经与它们关联的 TreeSet。如果密钥不在Map 中,则将其添加到Map 并返回null。所有这些都以原子方式发生。
    5. 如果我们从putIfAbsent() 得到一个null,这意味着我们成功地将我们的newTree 放入Map,所以我们...
    6. 将引用复制到treeSM
    7. 因为treeSM可以从多个线程中操作并且不是线程安全的集合,所以我们需要加锁。在这种情况下,使用 synchronized 块。
    8. 我们将MyClass 对象添加到treeSM

    重要的一点是,任何数量的线程都可以执行上面的行,无论它们是如何安排的,当您到达第 7 行时,您将始终拥有一个有效的 treeSM 引用,并且它将是相同的 @ 987654349@ 用于所有线程。

    这在 Java 8+ 中变得非常简单:

    TreeSet<MyClass> treeSM = messageMap.computeIfAbsent(key, x -> new TreeSet<>());
    
    synchronized (treeSM) {
        treeSM.add(sm);
    }
    

    【讨论】:

    • 感谢肖恩。尽管我还没有运行负载运行以进行正确的测试,但在初始测试中看起来还不错。谢谢!
    • 这修复了调用containsKey 后跟put 的check-then-act 错误,但是,此代码仍然损坏,因为它同时操作TreeSet,这不是线程安全的.
    • 请注意,您在 Java 8 示例中混淆了 TreeSetTreeMap。顺便说一句,Java 8 允许在密钥锁下执行更新,即messageMap.compute(key, (x,set) -&gt; { if(set==null) set=new TreeSet&lt;&gt;(); set.add(sm); return set; });,但为此,您必须确保在开始读取值之前已完成所有更新...
    【解决方案2】:

    我同意关于“效率”的另一个答案是完美

    但以防万一:“超级简单”的解决方案是简单地在相应代码周围放置某种锁定;通过将其放在 synchronized 方法中,或者使用锁定对象/同步块。

    【讨论】:

    • 如果您要这样做,您可能希望从使用ConcurrentHashMap 切换到仅使用HashMap,因为使用前者将不再有任何好处。
    • 不一定。有一个同步的 put 并不意味着其他线程可以同时对一个普通的 hashmap 进行安全读取?!
    • 其实我明白你现在在说什么了。我认为这会导致令人困惑的同步策略,但我认为它会起作用。
    猜你喜欢
    • 2010-11-20
    • 2014-04-24
    • 2014-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    相关资源
    最近更新 更多