Java 将对象重新放入ConcurrentHashMap是否会导致;发生在“之前”;记忆关系?

Java 将对象重新放入ConcurrentHashMap是否会导致;发生在“之前”;记忆关系?,java,concurrency,concurrenthashmap,Java,Concurrency,Concurrenthashmap,我正在使用现有的代码,该代码具有ConcurrentHashMap形式的对象存储。地图中存储了多线程使用的可变对象。没有两个线程试图通过设计同时修改对象。我关心的是线程之间修改的可见性 目前,对象的代码在“setters”(由对象本身保护)上进行同步。“getter”上没有同步,成员也不稳定。对我来说,这意味着能见度无法保证。但是,当修改对象时,它将重新放入贴图中(再次调用put()方法,使用相同的键)。这是否意味着当另一个线程将对象从地图中拉出时,它将看到修改 我在这里研究了stackover

我正在使用现有的代码,该代码具有ConcurrentHashMap形式的对象存储。地图中存储了多线程使用的可变对象。没有两个线程试图通过设计同时修改对象。我关心的是线程之间修改的可见性

目前,对象的代码在“setters”(由对象本身保护)上进行同步。“getter”上没有同步,成员也不稳定。对我来说,这意味着能见度无法保证。但是,当修改对象时,它将重新放入贴图中(再次调用
put()
方法,使用相同的键)。这是否意味着当另一个线程将对象从地图中拉出时,它将看到修改

我在这里研究了stackoverflow、in和java.util.concurrent的包描述。我基本上把自己搞糊涂了我想。。。但让我提出这个问题的最后一根稻草来自包装说明,它说:

在将对象放入任何并发集合之前,线程中的操作发生在另一个线程的集合中访问或移除该元素之后的操作之前

关于我的问题,“操作”是否包括在重新放置()之前对存储在映射中的对象所做的修改?如果所有这些都会导致线程之间的可见性,那么这是一种有效的方法吗?我对threads比较陌生,非常感谢您的评论

编辑:

谢谢大家的回复!这是我关于StackOverflow的第一个问题,对我很有帮助

我不得不同意他的回答,因为我认为这最清楚地说明了我的困惑。也就是说,在这种情况下,建立“之前发生”关系并不一定会影响修改的可见性。我的“标题问题”与文中所述的实际问题相比,结构不合理s的答案现在与我读到的一致:“为了确保所有线程都能看到共享可变变量的最新值,读线程和写线程必须在公共锁上同步”(第37页)。重新将对象放回贴图不会为修改插入对象的成员提供此公用锁


我欣赏所有关于改变的提示(不可变对象等),我完全同意。但是对于这种情况,正如我所提到的,由于仔细的线程处理,没有并发修改。一个线程修改一个对象,另一个线程稍后读取该对象(CHM是对象传送器)。我认为CHM不足以确保在我提供的情况下,后面执行的线程将看到第一个线程的修改。然而,我认为你们中的许多人正确地回答了标题问题。

我认为你们的问题更多地涉及到存储在地图中的对象,以及它们对并发访问的反应,而不是并发地图本身

如果您存储在映射中的实例具有同步的变体,但没有同步的访问器,那么我看不出它们如何能够像前面所描述的那样是线程安全的

从等式中取出
映射
,确定存储的实例本身是否是线程安全的

但是,当修改对象时,会将其重新放入映射中(再次调用put()方法,使用相同的键)。这是否意味着当另一个线程将对象从地图中拉出时,它将看到修改


这就是混乱的例证。重新放入映射的实例将由另一个线程从映射中检索。这是并发映射的保证。这与存储实例本身状态的可见性无关。

我的理解是,在重新放置后,它应该适用于所有GET,但这是一种非常不安全的同步方法

在重新放置之前,但在进行修改时,会发生什么。他们可能只看到一些更改,对象的状态可能不一致


如果可以,我建议在地图中存储不可变的对象。然后,任何get都将检索执行get时对象的当前版本。

在每次写入对象后调用
concurrHashMap.put
。但是,您没有指定在每次读取之前也调用
concurrHashMap.get
。这是必要的

所有形式的同步都是如此:两个线程中都需要一些“检查点”。只同步一个线程是无用的

我还没有检查ConcurrentHashMap的源代码,以确保
put
get
触发之前发生过,但它们应该这样做才合乎逻辑

但是,即使同时使用
put
get
,您的方法仍然存在问题。当您修改一个对象并且在它被
put
之前被另一个线程使用(处于不一致状态)时,就会出现问题。这是一个微妙的问题,因为您可能认为旧值会被读取,因为它尚未被
put
,并且不会导致问题。问题是,如果不同步,就不能保证获得一致的旧对象,而是行为未定义。JVM可以随时更新其他线程中对象的任何部分。只有在使用某些显式同步时,您才能确保以一致的方式跨线程更新值

你能做什么:
(1) 同步对代码中任何位置的对象的所有访问(getter和setter)。小心设置器:确保不能将对象设置为不一致的状态。例如,在设置名字和姓氏时,只有两个同步的setter是不够的:您必须获得
  919       public V get(Object key) {
  920           Segment<K,V> s; // manually integrate access methods to reduce overhead
  921           HashEntry<K,V>[] tab;
  922           int h = hash(key.hashCode());
  923           long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
  924           if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
  925               (tab = s.table) != null) {
  926               for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
  927                        (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
  928                    e != null; e = e.next) {
  929                   K k;
  930                   if ((k = e.key) == key || (e.hash == h && key.equals(k)))
  931                       return e.value;
  932               }
  933           }
  934           return null;
  935       }