我们需要使ConcurrentHashMap易变吗?

use*_*844 36 java concurrency java.util.concurrent

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

class Test {

    ConcurrentHashMap map;

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

    write() {
        map.put(key, object);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

Joh*_*int 32

如果你能做到,final那就去做吧.如果你不能成功final那么你需要做到volatile. volatile适用于字段分配,如果不是,final则有可能(至少根据JMM)一个线程对CHM字段的写入可能对另一个线程不可见.重申一下,这是ConcurrentHashMap字段分配而不是使用CHM.

话虽如此,你应该真的成功final.

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

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

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

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


Sot*_*lis 10

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

可以声明一个字段volatile,在这种情况下,Java Memory Model可以确保所有线程都看到变量的一致值(第17.4节).

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

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

检索反映了最近完成的更新操作的结果.(更正式地说,给定密钥的更新操作承担与该密钥报告更新值的任何(非空)检索之前发生的关系.


Ana*_*lyG 5

这里有2个子问题:参考visability到地图和值visability写入地图。

  1. 参考地图的可见性:

    我们需要做地图吗...

应该注意在多线程环境中安全发布引用。安全发布是指所有观察发布对象的读者都可以看到发布之前编写的所有值。根据JMM,可以通过以下几种方式安全地发布参考:

  1. 通过正确锁定的字段提供对参考的访问(JLS 17.4.5)
  2. 使用静态初始化程序进行初始化存储(JLS 12.4)(实际上不是我们的情况
  3. 通过volatile字段(JLS 17.4.5)或作为此规则的结果,通过AtomicX类(如AtomicReference)提供对引用的访问
  4. 将值初始化为最终字段(JLS 17.5)

因此,在您的情况下,“地图”参考没有正确发布。这可能会在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);
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,在第1页的情况下(使用正确锁定的字段“地图”时),可以使用普通的HashMap代替ConcurrentHashMap。但是,如果您仍想使用ConcurrentHashMap以获得更好的性能,那么如您所见,正确发布“地图”的最佳方法就是使该字段成为最终字段。

这是一篇有关Oracle专家安全发表的不错的文章,顺便说一句:http : //shipilev.net/blog/2014/safe-public-construction/

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

是否有可能在一个线程中看不到映射到另一个线程中的放置在另一个线程中?

不,如果您没有获得NPE(请参阅第1页)或正确发布了地图,那么读者总是会看到作者产生的所有更改,因为一对ConcurrentHashMap.put / get会产生适当的内存障碍/ Happens-Before边缘。

HashMap的相同问题

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


Bex*_*Bex 5

不,你不知道。

volatile意味着变量不能缓存在寄存器中,因此将始终“直写”到内存。这意味着一个线程对变量的更改将对其他线程可见。

在本例中,变量是对 Map 的引用。您始终使用同一个 Map,因此您不会更改引用 - 而是更改该 Map 的内容。(这就是说, Map 是可变的。)这也意味着您可以并且因此应该引用 Map final

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