ConcurrentHashMap 源码分析

  • 综述
    • hash冲突:桶位中保存的是Node, 发生hash冲突时, 利用Node.next构建单链表解决. 如果冲突超过阈值, 再转换为树结构.
    • 代码结构:实现中有很多类似 while { cas } 的代码
  • get

    • 通过volatile读操作保证线程安全, 具体又分为在null桶位处和Node的next处和put构成volatile写语义.
    • 将数组的引用声明为volatile (volatile Node[] table)只能保证对数组引用的操作符合volatile语义, 如 table=nextTab; 而对数据元素的操作(table[0]=xxx)则不满足.
      所以每次读取table中的node时, 要借助Unsafe.getObjectVolatile
    • Node的next同样被声明为volatile, 以保证get遍历链表时的线程安全.
       
  • put
    • 如果桶位为空, cas + volatile写换入
    • 如果桶位不为空, 对桶位首元素加锁. 加锁后重新volatile读桶位首元素并保证被锁的Node和重新读到的Node是同一个. (如果不同, 进行下一轮重试)
      在锁桶位首元素前, 可能另外的线程已经将该元素移除了; 所以在加锁之后, 一定要volatile读到最新的桶位首元素并进行判断. 
    • 遍历链表, 在遇到相同value的Node时终止; 如果不存在相同value的Node, 将新Node加到链表末尾. 
  • size  

    • 使用LongAdder的方式维护。类似增加投票箱缓解拥挤,也可以看成是根据线程hash值分桶位。

  • resize
    • 当元素数量超过一定数值后(默认是桶位数*0.75),就会进行resize.
    • resize不会重叠,即在一次resize结束之后,才能进行另一次resize,这通过volatile sizeCtl保证
    • resize建立新的桶位结构(新数组),然后将旧数组中的元素逐个拷贝到新数组中。
    • 对于旧的null桶位,cas换入ForwardingNode;非null桶位,桶位首元素加锁,复制完成后,换入ForwardingNode. 即拷贝完成的旧桶位值都被换成ForwardingNode,用于标识。
    • 对于get操作,如果遇到ForwardingNode,转到ForwardingNode指向的新数组中查找——即resize对get操作基本没有影响
    • 对于put操作,遇到ForwardingNode,先执行helpTransfer帮助copy。copy完成后,新数组会替换旧数组,再按正常流程put.
    • 每次获取桶位首元素的锁之后,必须判断桶位的首元素是否变化。如已变化,相当于cas失败,退回上层循环重做。

    

    

  

posted @ 2016-05-09 10:59  lispppppppppp  阅读(139)  评论(0)    收藏  举报