首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java 容器源码分析之ConcurrentHashMap(3)

Java 容器源码分析之ConcurrentHashMap(3)

put操作假设table已经初始化完成,put操作采用CAS+synchronized实现并发插入或更新操作,具体实现如下。
final V putVal(K key, V value, boolean onlyIfAbsent) {    if (key == null || value == null) throw new NullPointerException();    int hash = spread(key.hashCode());    int binCount = 0;    for (Node<K,V>[] tab = table;;) {        Node<K,V> f; int n, i, fh;        if (tab == null || (n = tab.length) == 0)            tab = initTable();        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {            if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null)))                break;                   // no lock when adding to empty bin        }        else if ((fh = f.hash) == MOVED)            tab = helpTransfer(tab, f);        ...省略部分代码    }    addCount(1L, binCount);    return null;}
  • hash算法static final int spread(int h) {return (h ^ (h >>> 16)) & HASH_BITS;}
  • table中定位索引位置,n是table的大小int index = (n - 1) & hash
  • 获取table中对应索引的元素f。
    Doug Lea采用Unsafe.getObjectVolatile来获取,也许有人质疑,直接table[index]不可以么,为什么要这么复杂?
    在java内存模型中,我们已经知道每个线程都有一个工作内存,里面存储着table的副本,虽然table是volatile修饰的,但不能保证线程每次都拿到table中的最新元素,Unsafe.getObjectVolatile可以直接获取指定内存的数据,保证了每次拿到数据都是最新的。
  • 如果f为null,说明table中这个位置第一次插入元素,利用Unsafe.compareAndSwapObject方法插入Node节点。
    • 如果CAS成功,说明Node节点已经插入,随后addCount(1L, binCount)方法会检查当前容量是否需要进行扩容。
    • 如果CAS失败,说明有其它线程提前插入了节点,自旋重新尝试在这个位置插入节点。
  • 如果f的hash值为-1,说明当前f是ForwardingNode节点,意味有其它线程正在扩容,则一起进行扩容操作。
  • 其余情况把新的Node节点按链表或红黑树的方式插入到合适的位置,这个过程采用同步内置锁实现并发,代码如下:synchronized (f) { if (tabAt(tab, i) == f) {     if (fh >= 0) {         binCount = 1;         for (Node<K,V> e = f;; ++binCount) {             K ek;             if (e.hash == hash &&                 ((ek = e.key) == key ||                  (ek != null && key.equals(ek)))) {                 oldVal = e.val;                 if (!onlyIfAbsent)                     e.val = value;                 break;             }             Node<K,V> pred = e;             if ((e = e.next) == null) {                 pred.next = new Node<K,V>(hash, key,                                           value, null);                 break;             }         }     }     else if (f instanceof TreeBin) {         Node<K,V> p;         binCount = 2;         if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,                                        value)) != null) {             oldVal = p.val;             if (!onlyIfAbsent)                 p.val = value;         }     } }}在节点f上进行同步,节点插入之前,再次利用tabAt(tab, i) == f判断,防止被其它线程修改。
    • 如果f.hash >= 0,说明f是链表结构的头结点,遍历链表,如果找到对应的node节点,则修改value,否则在链表尾部加入节点。
    • 如果f是TreeBin类型节点,说明f是红黑树根节点,则在树结构上遍历元素,更新或增加节点。
    • 如果链表中节点数binCount >= TREEIFY_THRESHOLD(默认是8),则把链表转化为红黑树结构。
返回列表