美文网首页
Java并发-20.ConcurrentHashMap

Java并发-20.ConcurrentHashMap

作者: 悠扬前奏 | 来源:发表于2019-05-28 20:09 被阅读0次

0. HashMap和HashTable

  • HashMap线程不安全
    多线程下HashMap的put操作可能导致Entry链表形成环形数据结构,next节点永不为空,就形成了死循环获取Entry,例如:
final HashMap<String, String> map = new HashMap<>(2);
        Thread t = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                new Thread(() -> map.put(UUID.randomUUID().toString(), ""), "ftf" + i).start();
            }
        }, "tft");
        t.start();
        t.join();
  • HashTable用Synchronized保证线程安全,线程激烈竞争时效率低下。

1. ConcurrentHashMap的结构

  • 由Segment数组和HashEntry数组组成
    • Segment
      • 一种ReentrantLock
      • 作为锁的角色
      • 数组结构,链表结构
    • HashEntry
      • 用于存储键值对
      • 一个Segment包含一个HashEntry
      • 一个HashEntry是一个链表结构的元素
  • 每个Segment守护着一个HashEntry数组的元素,修改HashSet前要现获取对应的Segment锁。


    image.png

2. ConcurrentHashMap初始化

  • 用initialCapacity,loadFactor,ConcurrencyLevel几个参数来初始化Segment数组,段偏移量segmentShift,段掩码segmentMask和每个segment里面的HashEntry来实现的

2.1 初始化segment数组

  • segment数组长度通过concurrencyLevel计算得到
  • 数组长度是大于concurrencyLevel的最小2的N次方
  • 长度最大值是2^16 = 65536

2.2 segmentShift和segmentMask初始化

  • sshift等于ssize从1左移的位数,默认等于4(concurrencyLevel默认等于16,左移4位)
  • segmentShift用于定位参与散列运算的位数,等于32 - sshift,默认也就是28
  • segmentMask是散列运算的掩码,等于ssize - 1,默认也就是15
  • ssize最大值65536,所以segmentShift最大值16,segmentMask最大值65535

2.3 初始化每个segment

  • 以两个参数初始化每个segment
    • initialCapacity是ConcurrentHashMap的初始化容量
    • loadfactor是每个segment的负载因子
  • HashEntry数组长度cap等于initialCapacity除以ssize的倍数c,c大于1就取大于等于c的2^N
  • segment容量threshold = (int) cap * loadFactory,默认initialCapacity=16,loadfactory=0.75,cap=1,threshold=0

2.4 定位Segment

  • 插入和获取元素的时候,需要先通过Hash算法定位Segment
  • ConcurrentHashMap会对元素的hashCode进行再一次Hash,目的是减少Hash冲突
  • 定位segment的hash算法:(hash >>> segmentShift) & segmentMask

3. ConcurrentHashMap的操作

  • get操作
    • 经过一次再散列,通过这个散列值定位到Segment,再通过散列算法定位到元素。
    • 高效,不需要加锁,除非读到了空值再加锁重读
    • get方法用到的共享变量都定义为volatile类型,例如Segment大小,存储HashEntry的value
  • put操作
    • put操作要加锁
    • 两步:
      • 判断是否需要扩容
      • 扩容:
        • 先创建一个容量是原容量两倍的数组,再将原数组元素散列后插入新数组
        • 不会对整个容器扩容,只对某个segment扩容
  • size操作
    • 先尝试2次通过不所处Segment的方式统计各Segment大小,如果统计过程中容器count发生了变化,再通过加锁的方式统计所有Segment大小
    • 判断count发生变化用了,modCount变量(就是CAS咯

相关文章

网友评论

      本文标题:Java并发-20.ConcurrentHashMap

      本文链接:https://www.haomeiwen.com/subject/mtvpzqtx.html