concurrentHashMap是线程安全且强一致性的吗?

看了文档,但是不是很明白,这个类可以在不阻塞线程的情况下提供线程安全的并发写,但是他没有对读进行同步,那么,假如读和写发生在同一个元素的时候,怎么办?这时候不做同步,那么数据肯定是脏的啊?还是我理解有错,大家都怎么使用这个类咧

说白了ConcurrentHashMap也是加锁的 但是不是全局锁 而是 分块锁(对桶里的数据分块 按块加锁 提高并发)

[code="java"] V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}[/code]

在写的时候,是不读就为null

在读的时候时,有同步锁
[code="java"]
V readValueUnderLock(HashEntry e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
[/code]

[code="java"]
/**

  • ConcurrentHashMap list entry. Note that this is never exported
  • out as a user-visible Map.Entry. *
  • Because the value field is volatile, not final, it is legal wrt
  • the Java Memory Model for an unsynchronized reader to see null
  • instead of initial value when read via a data race. Although a
  • reordering leading to this is not likely to ever actually
  • occur, the Segment.readValueUnderLock method is used as a
  • backup in case a null (pre-initialized) value is ever seen in
  • an unsynchronized access method. */ static final class HashEntry { final K key; final int hash; volatile V value; // <-- volatile final HashEntry next; }[/code]

按照我的理解,volatile可以保证一个线程总是能看到最后一个线程写入的数据,HashEntry的构造函数也没有escape this,这里readValueUnderLock只是用来backup的吧,实际上并不太可能会出现为null的情况

参考资料
http://stackoverflow.com/questions/12740844/why-get-method-in-concurrenthashmap-is-blocking
http://jsr166-concurrency.10961.n7.nabble.com/Numa-and-ReentrantLock-td5630.html
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalRight

ConcurrentHashMap 将 map中数据通过hash 散列到32个Segment 中,Segment 是 ConcurrentHashMap 一个内部类,他继承了ReentrantLock ,ConcurrentHashMap 将锁进行分化到每一个Segment中,只有对同一个Segment 元素的读写时 才会阻塞,否则是无阻塞的,这样就大大的提高了并发的执行速度。

V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;

V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry first = tab[index];
HashEntry e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;

            V oldValue;
            if (e != null) {
                oldValue = e.value;
                if (!onlyIfAbsent)
                    e.value = value;
            }
            else {
                oldValue = null;
                ++modCount;
                tab[index] = new HashEntry<K,V>(key, hash, first, value);
                count = c; // write-volatile
            }
            return oldValue;
        } finally {
            unlock();
        }
    }

get中的readValueUnderLock 和 put 都是互斥的 所以是安全的

单独的读写操作(get,put,putIfAbsent...)是完全线程安全且一致的,但是迭代时候则不是强一致的,迭代所遍历的不是迭代时刻的快照,而是各个segement的真是数据,所以迭代期间如有数据发生变更,如果变更的是已经遍历的segement则迭代过程不再感知这个变化,但如果变化发生在未遍历的segement,则本次迭代会感知到这个元素。另外一个基本常识,任意的组合操作,比如先get, 然后put,则不能保证强一致。此外这个类还有个特点,迭代过程中即使元素被修改,也不会抛出异常,其他一些集合则会抛出ConcurrentModificationException

检索会影响最近[color=red]完成的[/color] 更新操作的结果

所谓完成的,就是从put方法返回了

ConcurrentHashMap对K/V的读写都是加锁的,是一个可重入锁(ReenTrantLock),当然这是一个Segment(片段锁),只会锁定某一个K/V,基于CAS调度,也就是与CPU的直接打交道的,使用的是NonfairSync,所以能保证最大的吞吐量.

JDK中的任何类的线程安全只针对于类内部,比如你执行如下代码
map.put(1,1)//@1
int a = map.get(1)
map.put(1,2)//@2
int b = map.get(1)
a==b? true: false
如果是单线程,那么返回的是false,如果是多线程环境,那么你要注意了.
当线程A执行完@1的时候,可能CPU调度的时候[轮到线程B],线程B执行完@2,那么a的值就为2
所以在多线程环境,同步是由程序员来控制的.至于你说的强一致性,就看你自己的理解了

如读和写发生在同一个元素的时候,怎么办?这时候不做同步,那么数据肯定是脏的啊。

你理解的脏数据是有问题的,脏数据是指消费和生产的数据不一致,好比存钱和取钱,最后发现钱少了或钱多了,这就是脏数据。
而你描述的顶多算幻读,我在12:00打钱到你卡上,你12:05分才看到。幻读在任何系统都会出现,如果你一定不想有幻读,那么只有牺牲效率为代价。
这方面跟数据库的事务隔离级别就点类似。