【问题标题】:Is changing the value of a Map an atomic operation?更改 Map 的值是原子操作吗?
【发布时间】:2012-03-28 09:03:04
【问题描述】:

我想知道是否需要同步或使用并发类,或者相反,如果对映射的唯一修改正在更改,那么在多线程环境中使用非并发类并且不对映射进行同步是否是线程安全的地图的值。

我问这个的原因是 HashMap(和其他非并发地图文档)有这样的评论:

请注意,此实现不同步。 如果多个线程同时访问一个哈希映射,并且至少有一个 线程在结构上修改映射,它必须是 外部同步。 (结构修改是任何操作 添加或删除一个或多个映射;只是改变价值 与实例已包含的键关联的不是 结构修改。)这通常是通过 在一些自然封装地图的对象上进行同步。

这让我相信,如果修改不是结构性的(即没有添加或删除),我应该能够更新(非并发)地图无同步。

我读对了吗?即更新映射中的值是原子过程吗?

【问题讨论】:

    标签: java collections concurrency map atomic


    【解决方案1】:

    更新地图值不是一个原子过程。但是,让多个不同的线程各自尝试同时修改映射值不会因为并发错误而导致非常奇怪的异常或错误。例如,您不会导致其中一个键/值对消失,或从地图中删除随机元素。

    但是,一个线程在更新键/值对时所做的更新不一定对其他线程可见,除非正在进行一些其他同步(例如,如果值是 AtomicIntegers 之类的东西)。最重要的是,不能保证线程甚至会看到自己的更新,因为它们可能会被其他线程破坏。

    希望这会有所帮助!

    【讨论】:

    • @stryba- 你能详细说明一下吗? “不一致的状态”是什么意思?我的回答明确表明写入可能不可见,但我很确定您不会破坏地图。我可能错了,所以您能提供的任何东西都将不胜感激。
    • 不一致我的意思是你不会知道你的键/值是否实际存储在映射中,而另一个线程写入同一个映射,它们是否写入同一个键并不重要.也不能确保同一个线程看到它自己的写入值,如您所指示的。顺便说一句:-1 不是来自我,我是这么说的
    • 我见过一个例子,其中并发插入到 HashMap 实际上会破坏事情。是的,它确实破坏了集合,以至于由于丢失了一个存储桶而引发了 NullPointerExceptions。我正在尝试重新找到它……但与此同时,要知道,如果没有同步,对于像 HashMap 这样复杂的数据结构,您将无法依赖任何东西来工作。
    • @cHao - 文档中的关键词是“structurally 修改地图”。更新值不是结构变化;插入和删除键是。问题和答案都解决了这个问题。
    【解决方案2】:

    HashMap 中放入一些东西不是原子操作:

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    

    Collections#synchronizedMap 包裹你的HashMap 可能是值得的。

    【讨论】:

    • 我不确定我明白你的意思。如果键/值对已经存在并且值被替换,它似乎是原子的(尽管其他线程不一定可见)。该问题特别假设这不会导致结构修改。在这种情况下你的答案仍然成立吗?
    • 为了在大量读取时获得更好的性能,您可以使用自己的基于读/写锁定模式的同步。
    • @templatetypedef 替换值的操作是原子的,但对put的调用不是。如果您在并发环境中使用Map,同步绝不是一个坏主意。
    【解决方案3】:

    在更新方面,HashMapTreeMap 等 map 的实现既不是原子的也不是线程安全的,但是从 Java 1.8 开始,当您使用 ConcurrentHashMap 时,您可以实现原子更新操作。

    例如,以下方法将为特定键添加值,或者如果该键没有可用的先前值,则设置该值。

    ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();
    
    int addValue(String key, int value) {
        return map.compute(key, (k, v) -> v == null ? value : v + value);
    }
    

    【讨论】:

    • 你为什么说“从 Java 1.8 开始”?该类可以追溯到 Java 1.5。添加的新 1.8 方法似乎都是关于 lambda/streams 的,与原子更新无关。根据我的阅读,原子更新似乎是这个类的原始存在理由
    • @BasilBourque 我指的是“更新映射中的值是否是原子过程”,并对其自身的值赋予更多的权重,因为您需要不止一种方法来执行相同的操作在 Java 1.8 之前。但是,是的,您说得对,该类自 Java 1.5 以来就已经存在并且正在执行原子更新。我可能误读了这个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-05
    • 1970-01-01
    相关资源
    最近更新 更多