我们需要使ConcurrentHashMap易变吗?

时间:2015-04-02 04:11:22

标签: java concurrency java.util.concurrent

我们有一个共享的ConcurrentHashMap,由2个线程读取和写入。

class Test {

    ConcurrentHashMap map;

    read() {
        map.get(object);
    }

    write() {
        map.put(key, object);
    }
}

我们是否需要使地图变得易变,以便读者线程尽快看到一个线程的写入?

是否有可能在一个线程中放置到地图的位置不会被另一个线程看到或看得太晚? HashMap也有同样的问题。

4 个答案:

答案 0 :(得分:31)

如果你能成功final那么就这样做。如果您无法成功final,那么您需要将其设为volatilevolatile适用于字段分配,如果它不是final,那么有可能(至少每个JMM)一个线程对CHM字段的写入可能不可见另一个线程。重申一下,这是ConcurrentHashMap字段分配而不使用CHM。

话虽如此,你应该真正做到final

  

我们是否需要使地图变得易变,以便读者线程尽快看到一个线程的写入?

如果您所说的写入是使用CHM本身的变异方法(如putremove)完成的,那么您使字段volatile不会产生影响。所有内存可见性保证都在CHM中完成。

  

是否有可能在一个线程中放置到地图的位置不会被另一个线程看到或看得太晚? HashMap也有同样的问题。

不适用于ConcurrentHashMap。如果您同时使用普通的HashMap,请不要这样做。请参阅:http://mailinator.blogspot.com/2009/06/beautiful-race-condition.html

答案 1 :(得分:10)

volatile适用于对相应变量的读写操作之前的语义。

  

可以声明一个字段volatile,在这种情况下是Java内存模型   确保所有线程都看到变量的一致值   (§17.4)。

它与变量值引用的对象无关。因此,除非您在多个线程中修改变量,否则您不需要将其变为volatile

正如javadoc所述,所有ConcurrentHashMap方法都充当了内存障碍,

  

检索反映了最近完成的更新的结果   开始时的行动。 (更正式的是,更新   对于给定密钥的操作与之前的关系具有发生的关系   (非空)检索该密钥报告更新后的值。

答案 2 :(得分:4)

这里有两个子问题:参考对地图的可见性以及值的可见性写入地图。

  1. 对地图的引用的可见性:

      

    我们是否需要制作地图......

  2.      您关注多线程环境中引用的安全发布。安全发布意味着所有在发布之前编写的值对于观察发布对象的所有读者都是可见的。根据JMM,有以下几种方法可以安全地发布引用:

    1. 通过正确锁定的字段(JLS 17.4.5)
    2. 提供对引用的访问
    3. 使用静态初始化程序来执行初始化存储(JLS 12.4)(不是我们的实际情况
    4. 通过易失性字段(JLS 17.4.5)或作为此规则的结果,通过AtomicX类等AtomicX类提供对引用的访问
    5. 将值初始化为最终字段(JLS 17.5)
    6. 所以,在你的情况下你的"地图"参考文献未正确发布。这可能会在Test.read()或/和Test.write()中导致NullPointerException(它取决于哪个线程实例化ConcurrentHashMap并将其放入" map"字段)。正确的代码将是以下之一:

      //1. Provide access to the reference through a properly locked field
      class Test {
      
          ConcurrentHashMap map;
      
          synchronized void init(ConcurrentHashMap map) {
              this.map = map;
          }
      
          synchronized void read() {
              map.get(object);
          }
      
          synchronized void write() {
              map.put(key, object);
          }
      }
      
      // or
      class Test {
          ReadWriteLock rwl = new ReentrantReadWriteLock();
      
          ConcurrentHashMap map;
      
          void init(ConcurrentHashMap map) {
              rwl.writeLock().lock();
              this.map = map;
              rwl.writeLock().release();
          }
      
          void read() {
              rwl.readLock().lock();
              try {
                  map.get(object);
              } finally {
                rwl.readLock().release();
              }
          }
      
          void write() {
              rwl.writeLock().lock();
              try {
                  map.put(key, object);
              } finally {
                  rwl.writeLock().release();
              }
          }
      }
      
      // 3. Provide access to the reference via a volatile field
      class Test {
      
          volatile ConcurrentHashMap map; // or AtomicReference<ConcurrentHashMap> map = new AtomicReference();
      
          void init(ConcurrentHashMap map) {
              this.map = map;
          }
      
          void read() {
              map.get(object);
          }
      
          void write() {
              map.put(key, object);
          }
      }
      
      // 4. Initialize the value as a final field
      class Test {
      
          final ConcurrentHashMap map;
      
          Test(ConcurrentHashMap map) {
              this.map = map;
          }
      
          void read() {
              map.get(object);
          }
      
          void write() {
              map.put(key, object);
          }
      }
      

      当然,在p.1的情况下(使用正确锁定的字段&#34; map&#34;)而不是ConcurrentHashMap时,可以使用普通的HashMap。但是,如果您仍然希望使用ConcurrentHashMap以获得更好的性能,那么发布您的&#34; map&#34;的最佳方式正如你所看到的那样,正确的是让这个领域成为最终的。

      这是一篇关于安全发布Oracle文章的好文章,顺便说一下: http://shipilev.net/blog/2014/safe-public-construction/

      1. 写入地图的值的可见性:
      2.   

        是否可能无法看到或看到一个线程中的地图放置   很晚才有一个不同的主题?

        不,如果您没有获得NPE(请参阅第1页)或已正确发布地图,则读者始终会看到编写者所做的所有更改,因为一对ConcurrentHashMap.put / get会产生适当的内存障碍/发生 - 前缘。

          

        HashMap的相同问题

        HashMap根本不是线程安全的。方法HashMap.put / get以非线程安全的方式处理映射的内部状态(非原子,没有保证更改状态的线程间可见性),因此,您可能只是破坏了映射的状态。这意味着您必须使用适当的锁定机制(同步部分,ReadWriteLock等)来使用HashMap。并且,作为锁定的结果,您可以实现所需 - 读者始终可以看到编写器所产生的所有更改,因为这些锁会产生内存障碍/ Happens-Before edge。

答案 3 :(得分:4)

不,你不是。

volatile意味着变量不能缓存在寄存器中,因此将永远是&#34;直写&#34;记忆。这意味着一个线程对变量的更改将对其他线程可见。

在这种情况下,变量是Map的引用。您始终使用相同的地图,因此您不能更改参考 - 而是更改该地图的内容。 (也就是说,Map是 mutable 。)这也意味着您可以,因此应该引用Map final

ConcurrentHashMap与HashMap的不同之处在于,您通常可以安全地从中读取同时从不同线程向写入,而无需外部锁定。但是,如果您希望能够信任任何给定点的大小,执行检查然后写操作等,则需要自己设计。