ConcurrentHashMap 移除方法线程安全性 更新

问题描述

我正在尝试浏览 Spring 源代码 (5.2.8.RELEASE),发现以下代码

package org.springframework.core;

public class SimpleAliasRegistry implements AliasRegistry {
    /** Map from alias to canonical name. */
    private final Map<String,String> aliasMap = new ConcurrentHashMap<>(16);

    @Override
    public void removeAlias(String alias) {
        synchronized (this.aliasMap) {     // what if we remove such synchronized line ?
            String name = this.aliasMap.remove(alias);
            if (name == null) {
                throw new IllegalStateException("No alias '" + alias + "' registered");
            }
        }
    }
}

但我不确定的是removeAlias方法中有synchronized关键字的原因,真的有必要吗?如果我们删除 synchronized 关键字会怎样?会发生什么?

这是我的想法。 当我们调用它提供的方法时,ConcurrentHashMap 应该是线程安全的,例如 putgetremove。并且只有当我们在这里执行多个操作时才需要使用 synchronized 锁定对象,例如先获取然后放置或删除。但我们想让块运行无中断。

是我的想法错了,还是有什么原因可以这样设计 Spring removeAlias 方法,非常感谢。

更新

我还发现了更新的时间,开发者在修复问题时故意这样做的,SimpleAliasRegistry registeralias not atomic,这是他写出来的原因。

我使用同步保护 registeralias 和 removeAlias,允许现有的同步 resolveAliases 方法和其他方法可靠地看到一致的状态。

然而,我只是认为在 registeralias 中同步是必要的,但仍然无法说服为什么需要在 synchronized removeAlias 方法,谁能给我解释一下?

来源:https://github.com/spring-projects/spring-framework/issues/21119 [问题]

来源:https://github.com/spring-projects/spring-framework/commit/1b1a69a144f657d46c752f1c017f64d3302891d2 [针对该问题的更改]

解决方法

使用synchronized onConcurrentHashMap就像取消了ConcurrentHashMap的使用。所以在这种情况下,带有同步的 HashMap 将是相同的。

synchronized 的重点是锁定整个地图。 ConcurrentHashMap 的实现不需要锁定整个映射,因为它提供了段锁定,在并发访问时会自动锁定 ConcurrentHashMap 的一小部分。

也许项目存在错误,OP 通过锁定整个地图找到了快速修复。

多检查一点issue我认为OP希望在地图级别(get put)上使java方法原子化,并且担心java方法不会按预期运行,因此它继续前进并在任何地方同步整个地图。正如他所写的,他还可以利用 concurentHashMap 的计算(..)、computeIfAbsent(..)、computeIfPresent(..) 方法来避免这种情况,但我认为他走的是同步整个地图的快速道路。>

在他取消了 concurentHashMap 的主要用途后,他也可以在选择该修复程序后将 concurentHashMap 转换为 HashMap。

另请查看 here (SO thread) 以了解为什么 OP 一直在努力解决该问题。

,

在我看来: registerAlias/getAliases 都使用同步锁定此映射。 如果 removeAlias 方法没有同步,可能会出现这种感觉: 在调用this.aliasMap.remove之后的removeAlias中,已经成功删除别名,但是removeAlias没有完成,现在其他线程调用registerAlias在这个映射中放了一个相同的别名。所以 removeAlias 没有成功执行。 在 removeAlias 内部有不同的数据