Pour comprendre de ConcurrentHashMap

ConcurrentHashMap adopté avant jdk1.8 est des idées de blocage segmentés.

final Segment<K,V>[] segments;

Ce segment ReetrantLock hérité de verrouillage rentrante. Un segment est un sous-ensemble de la table de hachage, un segment dans le maintien d'un réseau de HashEntry. Et il y a un paramètre est ConcurrentLeve, lorsque la valeur de 16 quand elle, elle montre les segments de seize éléments, et jusqu'à 16 unités d'exécution concurrentes.

Chacun des éléments dans les segments maintient un tableau de HashEntry

transient volatile HashEntry<K,V>[] table;

Ceci est une classe interne statique.

static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
        //其他省略
}    

De même, nous examinons la structure de données du segment

Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
     this.loadFactor = lf;//负载因子
     this.threshold = threshold;//阈值
     this.table = tab;//主干数组即HashEntry数组
 }

Nous regardons le constructeur:

public ConcurrentHashMap(int initialCapacity,
                                 float loadFactor, int concurrencyLevel) {
    //判断参数是否合法
    if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
          throw new IllegalArgumentException();
    //MAX_SEGMENTS 为1<<16=65536,也就是最大并发数为65536
    if (concurrencyLevel > MAX_SEGMENTS)
        concurrencyLevel = MAX_SEGMENTS;
    //2的sshif次方等于ssize,例:ssize=16,sshift=4;ssize=32,sshif=5
    int sshift = 0;
    //ssize 为segments数组长度,根据concurrentLevel计算得出
    int ssize = 1;
    while (ssize < concurrencyLevel) {
        ++sshift;
        ssize <<= 1;
    }
    //segmentShift和segmentMask这两个变量在定位segment时会用到,后面会详细讲
    this.segmentShift = 32 - sshift;
    this.segmentMask = ssize - 1;
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    //计算cap的大小,即Segment中HashEntry的数组长度,cap也一定为2的n次方.
    int c = initialCapacity / ssize;
    if (c * ssize < initialCapacity)
        ++c;
    int cap = MIN_SEGMENT_TABLE_CAPACITY;
    while (cap < c)
        cap <<= 1;
    //创建segments数组并初始化第一个Segment,其余的Segment延迟初始化
    Segment<K,V> s0 =
        new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
                         (HashEntry<K,V>[])new HashEntry[cap]);
    Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
    UNSAFE.putOrderedObject(ss, SBASE, s0); 
    this.segments = ss;
}

Méthode d'initialisation a trois paramètres, si l'utilisateur ne spécifie pas une valeur par défaut est utilisée, initialCapacity 16, loadfactor 0,75 (facteur de charge, nécessaire de se référer à la dilatation), concurrentLevel 16.

Peut être vu à partir du code ci-dessus, la taille du segment du tableau ssize concurrentLevel déterminé par, mais pas nécessairement égale concurrentLevel, ssize doit être égale ou supérieure à la puissance minimum de 2 concurrentLevel. Par exemple: Par défaut concurrentLevel 16, 16 de la ssize, si concurrentLevel à 14, 16 ssize, si concurrentLevel 17, 32 de la ssize. Pourquoi la taille du segment du tableau doit être une puissance de 2? En fait, la clé est facile à appuyer sur par bit algorithme de hachage pour localiser le segment de l'indice.

A propos de segmentShift et segmentMask

Le rôle principal des variables globales segmentShift segmentMask et est utilisé pour localiser le segment, int j = (hachage >>> segmentShift) & segmentMask.
segmentMask : masque de segment, si la longueur des segments de matrice 16, le masque de segments est 16-1 = 15; les segments de longueur 32, le masque pour le segment 32-1 = 31. Toute la position binaire ainsi obtenu est égal à 1, peut mieux assurer l'uniformité de la table de hachage
segmentShift : Puissance du sshift égal à 2 ssize, segmentShift = 32 sshift. Si la longueur des segments 16, segmentShift = 32-4 = 28, si la longueur des segments 32, segmentShift = 32-5 = 27. La valeur de hachage calculée d'un maximum de 32 bits, décalage non signé droite segmentShift, cela signifie que seul un petit nombre de rétention élevé (les bits restants sont inutiles), les segments de masque positionné au niveau du bit segmentMask segment.

méthode put:

public V put(K key, V value) {
    Segment<K,V> s;
    //concurrentHashMap不允许key/value为空
    if (value == null)
        throw new NullPointerException();
    //hash函数对key的hashCode重新散列,避免差劲的不合理的hashcode,保证散列均匀
    int hash = hash(key);
    //返回的hash值无符号右移segmentShift位与段掩码进行位运算,定位segment
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
         (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}

Après calcul int j = (hachage >>> segmentShift) & segmentMask, sur l' accès à l'index est assigné à un segment, s = ensureSegment (j); verrouillage de recherche.
Insérez ensuite.
Ensuite , nous regardons

La méthode de vente du segment:

final V put(K key, int hash, V value, boolean onlyIfAbsent) {
 HashEntry<K,V> node = tryLock() ? null :
        scanAndLockForPut(key, hash, value);
        //tryLock不成功时会遍历定位到的HashEnry位置的链表(遍历主要是为了使CPU缓存链表),若找不到,则创建HashEntry。tryLock一定次数后(MAX_SCAN_RETRIES变量决定),则lock。若遍历过程中,由于其他线程的操作导致链表头结点变化,则需要重新遍历。
    V oldValue;
    try {
        HashEntry<K,V>[] tab = table;
        int index = (tab.length - 1) & hash;
        //定位HashEntry,可以看到,这个hash值在定位Segment时和在Segment中定位HashEntry都会用到,只不过定位Segment时只用到高几位。
        HashEntry<K,V> first = entryAt(tab, index);
        for (HashEntry<K,V> e = first;;) {
            if (e != null) {
                K k;
                if ((k = e.key) == key ||
                    (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            }
            else {
                if (node != null)
                    node.setNext(first);
                else
                    node = new HashEntry<K,V>(hash, key, value, first);
                int c = count + 1;
              //若c超出阈值threshold,需要扩容并rehash。扩容后的容量是当前容量的2倍。这样可以最大程度避免之前散列好的entry重新散列,具体在另一篇文章中有详细分析,不赘述。扩容并rehash的这个过程是比较消耗资源的。
                if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                    rehash(node);
                else
                    setEntryAt(tab, index, node);
                ++modCount;
                count = c;
                oldValue = null;
                break;
            }
        }
    } finally {
        unlock();
    }
    return oldValue;
}

méthode get:

public V get(Object key) {
    Segment<K,V> s; 
    HashEntry<K,V>[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    //先定位Segment,再定位HashEntry
    if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
        (tab = s.table) != null) {
        for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
                 (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
             e != null; e = e.next) {
            K k;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}
Publié 134 articles originaux · Praise gagné 91 · vues 160 000 +

Je suppose que tu aimes

Origine blog.csdn.net/weixin_44588495/article/details/104424368
conseillé
Classement