Java 更改贴图的值是原子操作吗?

Java 更改贴图的值是原子操作吗?,java,collections,concurrency,map,atomic,Java,Collections,Concurrency,Map,Atomic,我想知道是否需要同步或使用并发类,或者相反,如果对映射的唯一修改是更改映射的值,那么在多线程环境中使用非并发类而不对映射执行同步是否是线程安全的 我问这个问题的原因是HashMap(和其他非并发映射文档)有以下评论: 请注意,此实现是不同步的。 如果多个线程同时访问哈希映射,并且 线程在结构上修改映射,它必须是 外部同步。(A)结构修改是指任何操作 添加或删除一个或多个映射;仅更改值 与实例已包含的键关联的不是 结构修改。)这通常由 在自然封装贴图的某个对象上进行同步 这使我相信,如果修改不是结

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

我问这个问题的原因是HashMap(和其他非并发映射文档)有以下评论:

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

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


我读对了吗?i、 e.更新映射中的值是一个原子过程吗?

更新映射值不是一个原子过程。但是,如果有多个不同的线程同时尝试修改映射值,则不会由于并发错误而导致非常奇怪的异常或错误。例如,不会导致某个键/值对消失,也不会从映射中删除随机元素

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


希望这有帮助

将某物放入
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;
}
public V put(K键,V值){
if(key==null)
返回putForNullKey(值);
int hash=hash(key.hashCode());
int i=indexFor(散列,table.length);
for(条目e=表[i];e!=null;e=e.next){
对象k;
如果(e.hash==hash&((k=e.key)==key | | key.equals(k))){
V oldValue=e.value;
e、 价值=价值;
e、 记录存取(本);
返回旧值;
}
}
modCount++;
加法器(散列、键、值、i);
返回null;
}

HashMap
包装您的
HashMap可能是值得的。

map的实现,例如
HashMap
TreeMap
等,在更新时既不是原子的,也不是线程安全的,但是当您使用自Java 1.8以来的应用程序时,您可以实现原子更新操作

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

ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>();

int addValue(String key, int value) {
    return map.compute(key, (k, v) -> v == null ? value : v + value);
}
ConcurrentMap=new ConcurrentHashMap();
int addValue(字符串键,int值){
返回map.compute(键,(k,v)->v==null?值:v+value);
}

您将得到一个不一致的
映射状态
,即使在并发访问时并没有引发异常,但当您依赖以前存储在映射中的值时,您将得到疯狂的行为。@stryba-您能详细说明一下吗?“不一致状态”是什么意思?我的回答明确指出写入可能不可见,但我非常确定您不会损坏映射。我可能错了,所以你能提供的任何东西都会非常感激。如果不一致,我的意思是,当另一个线程写入同一个映射时,你将不知道你的键/值是否实际存储在映射中,而它们是否写入同一个键并不重要。也不能确保同一线程看到您指示的自己的写入值。顺便说一句:-1不是我写的,我说过我见过一个例子,在这个例子中,对HashMap的并发插入实际上破坏了一些东西。其中,是的,它确实破坏了集合,导致由于一个bucket丢失而引发NullPointerException。我正试图重新找到它……但与此同时,要知道,如果没有同步,数据结构像HashMap一样复杂,你就不能依靠任何东西来工作。@cHao-文档中的关键短语是“从结构上修改映射”。更新值不是一种结构性变化;插入和删除密钥是不正确的。问题和答案都提到了这一点。我不确定我明白你在这里的意思。如果键/值对已经存在,并且该值被替换,则看起来它将是原子的(尽管其他线程不一定可见)。问题特别假设这不会导致结构修改。在这种情况下,您的答案仍然有效吗?为了提高大量读取的性能,您可以使用自己的基于读/写锁定模式的同步。@templatetypedef替换值的操作是原子的,但对
put
的调用不是原子的。如果您在并发环境中使用
Map
,那么同步从来都不是一个坏主意。为什么您会说“自Java1.8以来”?这个类可以追溯到Java1.5。添加的新1.8方法似乎都是关于lambda/streams的,与原子更新无关。根据我的阅读,原子更新似乎是这个类最初存在的理由。@BasilBourque我指的是“映射中值的更新是一个原子过程吗”,并对它本身的值给予更多的重视,因为在Java 1.8之前,您需要不止一个方法来执行相同的操作。但是,您是对的,该类自Java1.5以来就一直存在,并且正在执行原子更新。我可能误解了这个问题。