//segment的定义
static final class Segment<K,V> extends ReentrantLock implements Serializable {
...忽略...
transient volatile HashEntry<K,V>[] table;
...忽略...
}
//concurrenthashmap改变segment中hashentry数组中某一下标处的hashentry对象
/**
* Sets the ith element of given table, with volatile write
* semantics. (See above about use of putOrderedObject.)
*/
static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
HashEntry<K,V> e) {
UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}
在remove操作中,当移除链表头节点时,会执行这段代码if (pred == null) setEntryAt(tab, index, next);
这个方法里面又会调用UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
,从而改变hashentry数组对应下标的hashentry对象。
我查阅资料:UNSAFE.putOrderedObject
和UNSAFE.putObjectVolatile
的区别是后者能保证可见性,前者是不能的。那这样1.7版本concurrenthashmap是怎么保证可见性的呢?比如,线程1remove操作修改了table[i]的链表头节点,指向了原链表的第二个hashentry对象。线程2紧接着进行get操作,读取这个table[i]的链表头节点。在这种场景下怎么保证线程2读取到的是remove过后的链表头节点(即原链表的第二个hashentry节点)。
这里我查阅了很多资料和帖子我都找不到我想要的答案,突然怀疑了自己对JMM理解的知识体系。。。。恳请悟空老师务必详细解答一下我的疑问