前言

HashMap非线程安全的,HashTable是线程安全的,所有涉及到多线程操作的都加上了synchronized关键字来锁住整个table,这就意味着所有的线程都在竞争一把锁,在多线程的环境下,它是安全的,但是无疑效率低下的。

ConcurrentHashMap(JDK1.7)

在JDK1.7中,ConcurrentHashMap的数据结构是由一个Segment数组和多个HashEntry组成的,如图:

Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,也就是锁分离技术,而每一个Segment元素存储的是HashEntry数组+ 链表。分段是一开始就确定的,后期不能再进行扩容(即并发度不能改变),但是单个Segment里面的数组是可以扩容的。

而JDK1.8中,是bin扩容(并发度可变)。

put

对于ConcurrentHashMap的数据插入,这里要进行两次Hash去定位数据的存储位置。

  1. static class Segment<K,V> extends ReentrantLock implements Serializable {
  2. }

从上Segment的继承体系可以看出,Segment实现了ReentrantLock,也就带有锁的功能,当执行put操作时,会进行第一次key的hash来定位Segment的位置,如果该Segment还没有初始化,即通过CAS操作进行赋值,然后进行第二次hash操作,找到相应的HashEntry的位置,这里会利用继承过来的锁的特性,在将数据插入指定的HashEntry位置时(链表的尾端),会通过继承ReentrantLock的tryLock()方法尝试去获取锁,如果获取成功就直接插入相应的位置,如果已经有线程获取该Segment的锁,那当前线程会以自旋的方式去继续的调用tryLock()方法去获取锁,超过指定次数就挂起,等待唤醒。

get

ConcurrentHashMap的get操作跟HashMap类似,只是ConcurrentHashMap第一次需要经过一次hash定位到Segment的位置,然后再hash定位到指定的HashEntry,遍历该HashEntry下的链表进行对比,成功就返回,不成功就返回null。

size

计算ConcurrentHashMap的元素大小是一个有趣的问题,因为他是并发操作的,就是在你计算size的时候,他还在并发的插入数据,可能会导致你计算出来的size和你实际的size有相差(在你return size的时候,插入了多个数据),要解决这个问题,JDK1.7版本用两种方案:

  1. try {
  2. for (;;) {
  3. if (retries++ == RETRIES_BEFORE_LOCK) {
  4. for (int j = 0; j < segments.length; ++j) ensureSegment(j).lock(); // force creation
  5. }
  6. sum = 0L;
  7. size = 0;
  8. overflow = false;
  9. for (int j = 0; j < segments.length; ++j) {
  10. Segment<K,V> seg = segmentAt(segments, j);
  11. if (seg != null) { sum += seg.modCount; int c = seg.count; if (c < 0 || (size += c) < 0)
  12. overflow = true;
  13. } }
  14. if (sum == last) break;
  15. last = sum; } }
  16. finally {
  17. if (retries > RETRIES_BEFORE_LOCK) {
  18. for (int j = 0; j < segments.length; ++j)
  19. segmentAt(segments, j).unlock();
  20. }
  21. }

  

  1. 第一种方案他会使用不加锁的模式去尝试多次计算ConcurrentHashMap的size,最多三次,比较前后两次计算的结果,结果一致就认为当前没有元素加入,计算的结果是准确的
  2. 第二种方案是如果第一种方案不符合,他就会给每个Segment加上锁,然后计算ConcurrentHashMap的size返回

ConcurrentHashMap(JDK1.8)

JDK1.8的实现已经摒弃了Segment的概念,而是直接用Node数组+链表+红黑树的数据结构来实现,并发控制使用Synchronized和CAS来操作,整个看起来就像是优化过且线程安全的HashMap,虽然在JDK1.8中还能看到Segment的数据结构,但是已经简化了属性,只是为了兼容旧版本;loadFactor仅用于构造函数中设定初始容量,已经不能影响扩容阈值,JDK1.8中阈值计算基本恒定为0.75;concurrencyLevel只影响初始容量,后续的并发度大小依赖于table数组的大小。

先看一些常量设计和数据结构:

  1. // node数组最大容量:2^30=1073741824
  2. private static final int MAXIMUM_CAPACITY = 1 << 30;
  3. // 默认初始值,必须是2的幕数
  4. private static final int DEFAULT_CAPACITY = 16;
  5. //数组可能最大值,需要与toArray()相关方法关联
  6. static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  7. //并发级别,遗留下来的,为兼容以前的版本
  8. private static final int DEFAULT_CONCURRENCY_LEVEL = 16;
  9. // 负载因子
  10. private static final float LOAD_FACTOR = 0.75f;
  11. // 链表转红黑树阀值,> 8 链表转换为红黑树
  12. static final int TREEIFY_THRESHOLD = 8;
  13. //树转链表阀值,小于等于6(tranfer时,lc、hc=0两个计数器分别++记录原bin、新binTreeNode数量,<=UNTREEIFY_THRESHOLD 则untreeify(lo))
  14. static final int UNTREEIFY_THRESHOLD = 6;
  15. static final int MIN_TREEIFY_CAPACITY = 64;
  16. private static final int MIN_TRANSFER_STRIDE = 16;
  17. private static int RESIZE_STAMP_BITS = 16;
  18. // 2^15-1,help resize的最大线程数
  19. private static final int MAX_RESIZERS = (1 << (32 - RESIZE_STAMP_BITS)) - 1;
  20. // 32-16=16,sizeCtl中记录size大小的偏移量
  21. private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
  22. // forwarding nodes的hash值
  23. static final int MOVED = -1;
  24. // 树根节点的hash值
  25. static final int TREEBIN = -2;
  26. // ReservationNode的hash值
  27. static final int RESERVED = -3;
  28. // 可用处理器数量
  29. static final int NCPU = Runtime.getRuntime().availableProcessors();
  30. //存放node的数组
  31. transient volatile Node<K,V>[] table;
  32. /*控制标识符,用来控制table的初始化和扩容的操作,不同的值有不同的含义
  33. *当为负数时:-1代表正在初始化,-N代表有N-1个线程正在 进行扩容
  34. *当为0时(默认值):代表当时的table还没有被初始化
  35. *当为正数时:表示初始化或者下一次进行扩容的大小
    */
  36. private transient volatile int sizeCtl;  

基本属性定义了ConcurrentHashMap的一些边界以及操作时的一些控制。

类图

Node是ConcurrentHashMap存储结构的基本单元,实现了Map.Entry接口,用于存储数据。它对value和next属性设置了volatile同步锁(与JDK7的Segment相同),它不允许调用setValue方法直接改变Node的value域,它增加了find方法辅助map.get()方法。

TreeNode继承于Node,但是数据结构换成了二叉树结构,它是红黑树的数据的存储结构,用于红黑树中存储数据,当链表的节点数大于8时会转换成红黑树的结构,他就是通过TreeNode作为存储结构代替Node来转换成黑红树。

TreeBin从字面含义中可以理解为存储树形结构的容器,而树形结构就是指TreeNode,所以TreeBin就是封装TreeNode的容器,它提供转换黑红树的一些条件和锁的控制。

ForwardingNode一个用于连接两个table的节点类。它包含一个nextTable指针,用于指向下一张表。而且这个节点的key value next指针全部为null,它的hash值为-1. 这里面定义的find的方法是从nextTable里进行查询节点,而不是以自身为头节点进行查找。

Unsafe和CAS

在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的。

  1. private static final sun.misc.Unsafe U;
  2. private static final long SIZECTL;
  3. private static final long TRANSFERINDEX;
  4. private static final long BASECOUNT;
  5. private static final long CELLSBUSY;
  6. private static final long CELLVALUE;
  7. private static final long ABASE;
  8. private static final int ASHIFT;
  9.  
  10. static {
  11. try {
  12. U = sun.misc.Unsafe.getUnsafe();
  13. Class<?> k = ConcurrentHashMap.class;
  14. SIZECTL = U.objectFieldOffset
  15. (k.getDeclaredField("sizeCtl"));
  16. TRANSFERINDEX = U.objectFieldOffset
  17. (k.getDeclaredField("transferIndex"));
  18. BASECOUNT = U.objectFieldOffset
  19. (k.getDeclaredField("baseCount"));
  20. CELLSBUSY = U.objectFieldOffset
  21. (k.getDeclaredField("cellsBusy"));
  22. Class<?> ck = CounterCell.class;
  23. CELLVALUE = U.objectFieldOffset
  24. (ck.getDeclaredField("value"));
  25. Class<?> ak = Node[].class;
  26. ABASE = U.arrayBaseOffset(ak);
  27. int scale = U.arrayIndexScale(ak);
  28. if ((scale & (scale - 1)) != 0)
  29. throw new Error("data type scale not a power of two");
  30. ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
  31. } catch (Exception e) {
  32. throw new Error(e);
  33. }
  34. }

ConcurrentHashMap定义了三个原子操作,用于对指定位置的节点进行操作。正是这些原子操作保证了ConcurrentHashMap的线程安全。

  1. // 获取tab数组的第i个node
    @SuppressWarnings("unchecked")
  2. static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
  3. return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
  4. }
  5. // 利用CAS算法设置i位置上的node节点。在CAS中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受
    // 你的修改,否则拒绝修改,即这个操作有可能不成功。
  6. static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
  7. Node<K,V> c, Node<K,V> v) {
  8. return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
  9. }
  10. // 利用volatile方法设置第i个节点的值,这个操作一定是成功的。
  11. static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
  12. U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
  13. } 

get

通过key获取value

  1. public V get(Object key) {
  2. Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    // 计算hash值
  3. int h = spread(key.hashCode());
    // 如果tab不空并且bin里面的节点不为空
  4. if ((tab = table) != null && (n = tab.length) > 0 &&
  5. (e = tabAt(tab, (n - 1) & h)) != null) {
  6. if ((eh = e.hash) == h) { // 如果bin里面的头节点就是需要查询的value
  7. if ((ek = e.key) == key || (ek != null && key.equals(ek)))
  8. return e.val;
  9. }
  10. else if (eh < 0) // eh < 0 只有可能是MOVED(-1)或TREEBIN(-2)
  11. return (p = e.find(h, key)) != null ? p.val : null;
  12. while ((e = e.next) != null) { // 链表
  13. if (e.hash == h &&
  14. ((ek = e.key) == key || (ek != null && key.equals(ek))))
  15. return e.val;
  16. }
  17. }
  18. return null;
  19. }

put

首先看一下put的源码:根据hash值计算这个新插入的点在table中的位置i,如果i位置是空的,直接放进去,否则进行判断,如果i位置是树节点,按照树的方式插入新的节点,否则把i插入到链public V put(K key, V value) {      return putVal(key, value, false);

  1. }
  2.  
  3. /** Implementation for put and putIfAbsent */
  4. final V putVal(K key, V value, boolean onlyIfAbsent) {
    // key和value不允许null
  5. if (key == null || value == null) throw new NullPointerException();
  6. int hash = spread(key.hashCode()); // 计算hash值
  7. int binCount = 0;
  8. for (Node<K,V>[] tab = table;;) { // 死循环,何时插入成功,才跳出
  9. Node<K,V> f; int n, i, fh;
  10. if (tab == null || (n = tab.length) == 0)
  11. tab = initTable(); // table是在首次插入元素的时候初始化,lazy
  12. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
  13. if (casTabAt(tab, i, null, // 如果这个位置没有值,直接放进去,由CAS保证线程安全,不需要加锁
  14. new Node<K,V>(hash, key, value, null)))
  15. break; // no lock when adding to empty bin
  16. }
  17. else if ((fh = f.hash) == MOVED) // 参与扩容
  18. tab = helpTransfer(tab, f);
  19. else {
  20. V oldVal = null;
    // 节点上锁,这里的节点可以理解为hash值相同组成的链表的头节点,锁的粒度为头节点。
  21. synchronized (f) {
  22. if (tabAt(tab, i) == f) {
  23. if (fh >= 0) { // 普通Node的hash值为key的hash值大于零,而ForwardingNode的是-1,TreeBin是-2
  24. binCount = 1;
  25. for (Node<K,V> e = f;; ++binCount) { // 主要遍历链表到最后,然后增加新节点
  26. K ek;
  27. if (e.hash == hash &&
  28. ((ek = e.key) == key ||
  29. (ek != null && key.equals(ek)))) {
  30. oldVal = e.val;
  31. if (!onlyIfAbsent)
  32. e.val = value;
  33. break;
  34. }
  35. Node<K,V> pred = e;
  36. if ((e = e.next) == null) { // 在链表最后插入新节点
  37. pred.next = new Node<K,V>(hash, key,
  38. value, null);
  39. break;
  40. }
  41. }
  42. }
  43. else if (f instanceof TreeBin) { // 如果是数节点,就按照树的方式插入节点。
  44. Node<K,V> p;
  45. binCount = 2;
  46. if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
  47. value)) != null) {
  48. oldVal = p.val;
  49. if (!onlyIfAbsent)
  50. p.val = value;
  51. }
  52. }
  53. }
  54. }
  55. if (binCount != 0) {
  56. if (binCount >= TREEIFY_THRESHOLD)// 如果链表长度大于等于8,则试着把链表转树
  57. treeifyBin(tab, i);
  58. if (oldVal != null)
  59. return oldVal;
  60. break;
  61. }
  62. }
  63. }
  64. addCount(1L, binCount);// 数量 + 1;
  65. return null;
  66. }

JDK8中的实现也是锁分离思想,只是锁住的是一个node,而不是JDK7中的Segment;锁住Node之前的操作是基于在volatile和CAS之上无锁并且线程安全的。

put操作的流程图如下:

从put可以看出有几个操作比较重要,下面我们就重点讲解这几个方法:initTable,helpTransfer,treeifyBin,addCount

initTable初始化

初始化方法主要应用了关键属性sizeCtl 如果这个值小于0,表示其他线程正在进行初始化,就放弃这个操作。在这也可以看出ConcurrentHashMap的初始化只能由一个线程完成。如果获得了初始化权限,就用CAS方法将sizeCtl置为-1,防止其他线程进入。初始化数组后,将sizeCtl的值改为0.75*n。

  1. private final Node<K,V>[] initTable() {
  2. Node<K,V>[] tab; int sc;
  3. while ((tab = table) == null || tab.length == 0) {
  4. if ((sc = sizeCtl) < 0)// sizeCtl < 0 标示有其他线程正在进行初始化操作,把线程让出cpu,对于table的厨师操作,只能有一个线程在进行
  5. Thread.yield(); // lost initialization race; just spin
  6. else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { // 利用CAS把sizeCtl设置为-1,标示本线程正在进行初始化,同一个时刻只有一个线程能更新成功,失败的重新循环,发现sizeCtl已经 < 0
  7. try {
    // 为什么还要判断,因为:如果走到下面的finally改变了sizeCtl值,有可能其他线程是会进入这个逻辑的
  8. if ((tab = table) == null || tab.length == 0) {
  9. int n = (sc > 0) ? sc : DEFAULT_CAPACITY; // 默认大小是16
  10. @SuppressWarnings("unchecked")
  11. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
  12. table = tab = nt;
  13. sc = n - (n >>> 2); // 0.75*n,下一次扩容阈值
  14. }
  15. } finally {
  16. sizeCtl = sc;
  17. }
  18. break;
  19. }
  20. }
  21. return tab;
  22. }

transfer

当ConcurrentHashMap容量不足的时候,需要对table进行扩容,它支持并发扩容,却没有锁。

扩容的场景:

(1)往hashMap中成功插入一个key/value节点时,有可能触发扩容动作:所在链表的元素个数达到了阈值 8,则会调用treeifyBin方法把链表转换成红黑树,不过在结构转换之前,会对数组长度进行判断,如果小于64,则优先扩容,而不是链表转树。

(2)新增节点之后,会调用addCount方法记录元素个数,并检查是否需要进行扩容,当数组元素个数达到阈值时,会触发transfer方法,重新调整节点的位置。

整个扩容操作分为两个部分 

  • 第一部分是构建一个nextTable,它的容量是原来的两倍,这个操作是单线程完成的。这个单线程的保证是通过RESIZE_STAMP_SHIFT这个常量经过一次运算来保证的,这个地方在后面会有提到;

  • 第二个部分就是将原来table中的元素复制到nextTable中,这里允许多线程进行操作。
  1. // 扩容操作
  2. private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
  3. int n = tab.length, stride;
    // 每核处理的量小于16,则强制赋值16
  4. if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
  5. stride = MIN_TRANSFER_STRIDE; // subdivide range
  6. if (nextTab == null) { // 构造一个2倍的Node数组
  7. try {
  8. @SuppressWarnings("unchecked")
  9. Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
  10. nextTab = nt;
  11. } catch (Throwable ex) { // try to cope with OOME
  12. sizeCtl = Integer.MAX_VALUE;
  13. return;
  14. }
  15. nextTable = nextTab;
  16. transferIndex = n;
  17. }
  18. int nextn = nextTab.length;
    // 初始化fwd节点,其中保存了新数组nextTable的引用,在处理完每个bin的节点之后
    // 当做占位节点,表示该bin已经被处理过了
  19. ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
  20. boolean advance = true;
  21. boolean finishing = false; // to ensure sweep before committing nextTab
    // 通过for循环处理每个bin中的链表元素
    // i:表示当前处理的bin序号,bound:表示需要处理的bin边界,处理是从数组的最后一个bin开始
  22. for (int i = 0, bound = 0;;) {
  23. Node<K,V> f; int fh;
    // 这个while其实是确定下一个需要处理的bin
  24. while (advance) {
  25. int nextIndex, nextBound;
  26. if (--i >= bound || finishing)
  27. advance = false;
  28. else if ((nextIndex = transferIndex) <= 0) {
  29. i = -1;
  30. advance = false;
  31. }
  32. else if (U.compareAndSwapInt
  33. (this, TRANSFERINDEX, nextIndex,
  34. nextBound = (nextIndex > stride ?
  35. nextIndex - stride : 0))) {
  36. bound = nextBound;
  37. i = nextIndex - 1;
  38. advance = false;
  39. }
  40. }
  41. if (i < 0 || i >= n || i + n >= nextn) {
  42. int sc;
  43. if (finishing) { // 整个扩容结束的标志
  44. nextTable = null;
  45. table = nextTab;
  46. sizeCtl = (n << 1) - (n >>> 1);
  47. return;
  48. }
  49. if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
  50. if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
  51. return;
  52. finishing = advance = true;
  53. i = n; // recheck before commit
  54. }
  55. }
  56. else if ((f = tabAt(tab, i)) == null) // 如果bin为空,则通过CAS设置fwd,表示已经处理过了
  57. advance = casTabAt(tab, i, null, fwd);
  58. else if ((fh = f.hash) == MOVED) // 如果已经处理过了则设置fwd节点,其hash值为MOVED(-1)
  59. advance = true; // already processed
  60. else {
  61. synchronized (f) { // 对bin的头节点(链表的头节点或数的根节点)加锁
  62. if (tabAt(tab, i) == f) {
  63. Node<K,V> ln, hn;
  64. if (fh >= 0) { // 链表的操作:构建2个链表,一个是原链表,另一个是原链表的反序排列
  65. int runBit = fh & n;
  66. Node<K,V> lastRun = f;
  67. for (Node<K,V> p = f.next; p != null; p = p.next) {
  68. int b = p.hash & n;
  69. if (b != runBit) {
  70. runBit = b;
  71. lastRun = p;
  72. }
  73. }
  74. if (runBit == 0) {
  75. ln = lastRun;
  76. hn = null;
  77. }
  78. else {
  79. hn = lastRun;
  80. ln = null;
  81. }
  82. for (Node<K,V> p = f; p != lastRun; p = p.next) {
  83. int ph = p.hash; K pk = p.key; V pv = p.val;
  84. if ((ph & n) == 0)
  85. ln = new Node<K,V>(ph, pk, pv, ln);
  86. else
  87. hn = new Node<K,V>(ph, pk, pv, hn);
  88. }
  89. setTabAt(nextTab, i, ln); // 在nextTable的i位置插入链表
  90. setTabAt(nextTab, i + n, hn); // 在nextTable的i+n位置插入链表
  91. setTabAt(tab, i, fwd); // 在tab的i位置插入fwd节点,标示处理过了
  92. advance = true;
  93. }
  94. else if (f instanceof TreeBin) { // 树的操作
  95. TreeBin<K,V> t = (TreeBin<K,V>)f;
  96. TreeNode<K,V> lo = null, loTail = null;
  97. TreeNode<K,V> hi = null, hiTail = null;
  98. int lc = 0, hc = 0;
  99. for (Node<K,V> e = t.first; e != null; e = e.next) {
  100. int h = e.hash;
  101. TreeNode<K,V> p = new TreeNode<K,V>
  102. (h, e.key, e.val, null, null);
  103. if ((h & n) == 0) {
  104. if ((p.prev = loTail) == null)
  105. lo = p;
  106. else
  107. loTail.next = p;
  108. loTail = p;
  109. ++lc;
  110. }
  111. else {
  112. if ((p.prev = hiTail) == null)
  113. hi = p;
  114. else
  115. hiTail.next = p;
  116. hiTail = p;
  117. ++hc;
  118. }
  119. }
    // 如果树<=6,则树转数组
  120. ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
  121. (hc != 0) ? new TreeBin<K,V>(lo) : t;
  122. hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
  123. (lc != 0) ? new TreeBin<K,V>(hi) : t;
  124. setTabAt(nextTab, i, ln);
  125. setTabAt(nextTab, i + n, hn);
  126. setTabAt(tab, i, fwd);
  127. advance = true;
  128. }
  129. }
  130. }
  131. }
  132. }
  133. }

  

状态变化图:

(1)初始化有一个16大小的数组:

(2)创建一个二倍大小的nextTable,并且new ForwardingNode<K,V>(nextTab)

(3)从后往前移动tab中元素到nextTable,比如:已经把tab[10-15]移动到nextTable中的状态图为:

treeifyBin 

在put操作中,如果发现链表结构中的元素超过8个,则会把链表转换为红黑树,便于提高查询效率。

  1. private final void treeifyBin(Node<K,V>[] tab, int index) {
  2. Node<K,V> b; int n, sc;
  3. if (tab != null) {
    // 如果tab长度小于64,则优先扩容(2倍扩展),而不是链表转树。
  4. if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
  5. tryPresize(n << 1);
  6. else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
  7. synchronized (b) { // 对bin的头节点加锁,保证整个红黑树的建立是同步的
  8. if (tabAt(tab, index) == b) {
  9. TreeNode<K,V> hd = null, tl = null; // 创建TreeNode节点
  10. for (Node<K,V> e = b; e != null; e = e.next) {
  11. TreeNode<K,V> p =
  12. new TreeNode<K,V>(e.hash, e.key, e.val,
  13. null, null);
  14. if ((p.prev = tl) == null)
  15. hd = p;
  16. else
  17. tl.next = p;
  18. tl = p;
  19. }
  20. setTabAt(tab, index, new TreeBin<K,V>(hd)); // 最终设置bin头结点的值为TreeBin
  21. }
  22. }
  23. }
  24. }
  25. }

addCount

把当前ConcurrentHashMap元素个数 + 1,主要有2个步骤:(1)更新baseCount值(2)检测是否进行扩容

  1. private final void addCount(long x, int check) {
  2. CounterCell[] as; long b, s;
    // CAS更新baseCount值
  3. if ((as = counterCells) != null ||
  4. !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
  5. CounterCell a; long v; int m;
  6. boolean uncontended = true;
  7. if (as == null || (m = as.length - 1) < 0 ||
  8. (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
  9. !(uncontended =
  10. U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
  11. fullAddCount(x, uncontended);
  12. return;
  13. }
  14. if (check <= 1)
  15. return;
  16. s = sumCount();
  17. }
  18. if (check >= 0) { // 检测是否需要扩容
  19. Node<K,V>[] tab, nt; int n, sc;
  20. while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
  21. (n = tab.length) < MAXIMUM_CAPACITY) {
  22. int rs = resizeStamp(n);
  23. if (sc < 0) {
  24. if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
  25. sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
  26. transferIndex <= 0)
  27. break;
  28. if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
  29. transfer(tab, nt);
  30. }
  31. else if (U.compareAndSwapInt(this, SIZECTL, sc,
  32. (rs << RESIZE_STAMP_SHIFT) + 2))
  33. transfer(tab, null);
  34. s = sumCount();
  35. }
  36. }
  37. }

  

size

最后,我们看看size方法

多线程-ConcurrentHashMap(JDK1.8)的更多相关文章

  1. ConcurrentHashMap(JDK1.8)为什么要放弃Segment

    今天看到一篇博客:jdk1.8的HashMap和ConcurrentHashMap,我想起了前段时间面试的一个问题:ConcurrentHashMap(JDK1.8)为什么要使用synchronize ...

  2. Java泛型底层源码解析--ConcurrentHashMap(JDK1.7)

    1. Concurrent相关历史 JDK5中添加了新的concurrent包,相对同步容器而言,并发容器通过一些机制改进了并发性能.因为同步容器将所有对容器状态的访问都串行化了,这样保证了线程的安全 ...

  3. java多线程 -- ConcurrentHashMap 锁分段 机制

    hashtable效率低ConcurrentHashMap 线程安全,效率高 Java 5.0 在 java.util.concurrent 包中提供了多种并发容器类来改进同步容器 的性能. Conc ...

  4. concurrenthashmap jdk1.8

    参考:https://www.jianshu.com/p/c0642afe03e0 CAS的思想很简单:三个参数,一个当前内存值V.旧的预期值A.即将更新的值B,当且仅当预期值A和内存值V相同时,将内 ...

  5. 【Java多线程】JDK1.5并发包API杂谈

    并发与并行 并发 一个或多个处理器执行更多的任务(通过划分时间片来执行更多的任务),从逻辑上实现同时运行: 如,N个并发请求在一个两核CPU上: 并行 N个处理器分别同时执行N个任务,从物理上实现同时 ...

  6. 6.ConcurrentHashMap jdk1.7

    6.1 hash算法 就是把任意长度的输入,通过散列算法,变换成固定长度的输出,该输出就是散列值.这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所 ...

  7. Java 集合系列之五:Map基本操作

    1. Java Map 1. Java Map 重要观点 Java Map接口是Java Collections Framework的成员.但是它不是Collection 将键映射到值的对象.一个映射 ...

  8. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进(转)

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

  9. Java并发编程总结4——ConcurrentHashMap在jdk1.8中的改进

    一.简单回顾ConcurrentHashMap在jdk1.7中的设计 先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示: 每一个segment都是一个HashE ...

随机推荐

  1. 利用Visual Studio Natvis 框架简化C++的变量调试工作

    相信用C++开发过UI界面的程序员都对其变量调试工作头痛不已,由于复杂的继承关系,要查看到某个变量往往需要一系列的层层深入的点击,如下图就是查看TextBox的Text的例子: 为了查看Text属性, ...

  2. loadrunner11操作手册

    一:操作 或者 增加用户数的方法 一:仅对单个场景增加用户数 二:同时对多个场景增加用户数 第一步: 第二步: 二:脚本编写示例 Action() { int nHttpRetCode; web_re ...

  3. druid+spring配置

    web.xml配置 <!-- Druid,监控数据库,以及WEB访问连接信息 --> <!-- 配置好后访问 http://ip:port/projectName/druid/ind ...

  4. Oracle数据库查看用户状态

    一.当前ORACLE用户的状态可查看视图DBA_USERS;一般情况下在使用的正常用户均处于OPEN状态. 1 SQL> select username,account_status from  ...

  5. static_cast ,reinterpret_cast

    用法:static_cast < type-id > ( expression ) 该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性.它主要有 ...

  6. Android4.4系统源码百度网盘下载

    众所周知.Android如今非常火,肯定也有非常多android开发人员像我一样想研究android系统的源码.可是假设依照Google官方站点http://source.android.com/so ...

  7. linux(虚拟机中)与windows共享文件两种方法

      Windows 下用 SourceInsight 与 Linux 协作编码     习惯了用SourceInsight 读写代码,在Linux下一时没找到类似的工具,vi的操作也不熟,偶尔看看或小 ...

  8. [Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘终结篇:UniLua热更新全然解读

    ---------------------------------------------------------------------------------------------------- ...

  9. Ubuntu12.04 Bugzilla 和 TestOpia的安装步骤

    1.        安装apache User@ubuntu:$  sudo apt-get install apache2 注:安装完以后能够通过http://192.168.128.128/ 来訪 ...

  10. recess----3.Controller如何和View关联

    上一篇我们讲到,Recess框架里面的controller就是一个典型的MVC框架的controller,它负责处理从浏览器(或其它客户端)过来的request,然后调用引用model里面的方法来进行 ...