它维护了一个 segment 数组,每个 segment 对应一把锁

    • 优点:如果多个线程访问不同的 segment,实际是没有冲突的,这与 jdk8 中是类似的
    • 缺点:Segments 数组默认大小为16,这个容量初始化指定后就不能改变了,并且不是懒惰初始化
      1. public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
      2. if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
      3. throw new IllegalArgumentException();
      4. if (concurrencyLevel > MAX_SEGMENTS)
      5. concurrencyLevel = MAX_SEGMENTS;
      6. // ssize 必须是 2^n, 即 2, 4, 8, 16 ... 表示了 segments 数组的大小
      7. int sshift = 0;
      8. int ssize = 1;
      9. while (ssize < concurrencyLevel) {
      10. ++sshift;
      11. ssize <<= 1;
      12. }
      13. // segmentShift 默认是 32 - 4 = 28
      14. this.segmentShift = 32 - sshift;
      15. // segmentMask 默认是 15 即 0000 0000 0000 1111
      16. this.segmentMask = ssize - 1;
      17. if (initialCapacity > MAXIMUM_CAPACITY)
      18. initialCapacity = MAXIMUM_CAPACITY;
      19. int c = initialCapacity / ssize;
      20. if (c * ssize < initialCapacity)
      21. ++c;
      22. int cap = MIN_SEGMENT_TABLE_CAPACITY;
      23. while (cap < c)
      24. cap <<= 1;
      25. // 创建 segments and segments[0]
      26. Segment<K,V> s0 =
      27. new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
      28. (HashEntry<K,V>[])new HashEntry[cap]);
      29. Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
      30. UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
      31. this.segments = ss; }
      构造完成后如图
      image.png
      可以看到 ConcurrentHashMap 没有实现懒惰初始化,空间占用不友好
      其中 this.segmentShift 和 this.segmentMask 的作用是决定将 key 的 hash 结果匹配到哪个 segment
      例如,根据某一 hash 值求 segment 位置,先将高位向低位移动 this.segmentShift 位
      image.png
      结果再与 this.segmentMask 做位于运算,最终得到 1010 即下标为 10 的 segment
      image.png