concurrentHashMap1.7

poner método 

    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
//计算hsah
        int hash = hash(key);
 //通过hash值运算把键值对定位到segment[j]片段上
        int j = (hash >>> segmentShift) & segmentMask;
//检查segment[j]是否已经初始化了,没有的话调用ensureSegment初始化segment[j]
        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);
    }

 El método sureSegment determina

 private Segment<K,V> ensureSegment(int k) {
        final Segment<K,V>[] ss = this.segments;
        long u = (k << SSHIFT) + SBASE; // raw offset
        Segment<K,V> seg;
        //按照segment[0]的HashEntry数组长度和加载因子初始化Segment[k]
        if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
            Segment<K,V> proto = ss[0]; // use segment 0 as prototype
            int cap = proto.table.length;
            float lf = proto.loadFactor;
            int threshold = (int)(cap * lf);
            HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
            if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                == null) { // recheck
                Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
                while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
                       == null) {
                    if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
                        break;
                }
            }
        }
        return seg;
    }

Llame al método put del segmento para insertar pares clave-valor en la matriz HashEntry del segmento 

    final V put(K key, int hash, V value, boolean onlyIfAbsent) {
        //Segment继承ReentrantLock,尝试获取独占锁
        HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
        V oldValue;
        try {
            HashEntry<K,V>[] tab = table;
            //定位键值对在HashEntry数组上的位置
            int index = (tab.length - 1) & hash;
            //获取这个位置的第一个键值对
            HashEntry<K,V> first = entryAt(tab, index);
            for (HashEntry<K,V> e = first;;) {
                if (e != null) {//此处有链表结构,一直循环到e==null
                    K k;
                    //存在与待插入键值对相同的键,则替换value
                    if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                        oldValue = e.value;
                        if (!onlyIfAbsent) {//onlyIfAbsent默认为false
                            e.value = value;
                            ++modCount;
                        }
//并退出循环
                        break;
                    }
//将e.next赋值给e
                    e = e.next;
                }
//当e为null
                else {
                    //node不为null,则表示原链表有值,并且该值未在链表中,设置node的next为first,node为当前链表的头节点
                    if (node != null)
                        node.setNext(first);
                    //node为null,表示该下标没有值,创建头节点,指定next为first,node为当前链表的头节点
                    else
                        node = new HashEntry<K,V>(hash, key, value, first);
                    int c = count + 1;
                    //扩容条件 (1)entry数量大于阈值 (2) 当前数组tab长度小于最大容量。满足以上条件就扩容
                    if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                        //扩容
                        rehash(node);
                    else
                        //tab的index位置设置为node,
                        setEntryAt(tab, index, node);
                    ++modCount;
                    count = c;
                    oldValue = null;
                    break;
                }
            }
        } finally {
//独占锁最后在finally中释放锁
            unlock();
        }
        return oldValue;
    }
  • scanAndLockForPut (clave K, int hash, valor V)

Intente adquirir el bloqueo a través de CAS sin exceder el número máximo de reintentos MAX_SCAN_RETRIES

private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
        //first,e:键值对的hash值定位到数组tab的第一个键值对
        HashEntry<K,V> first = entryForHash(this, hash);
        HashEntry<K,V> e = first;
        HashEntry<K,V> node = null;
        int retries = -1; // negative while locating node
        //线程尝试通过CAS获取锁
        while (!tryLock()) {
            HashEntry<K,V> f; // to recheck first below
            if (retries < 0) {
                //当e==null或key.equals(e.key)时retry=0,走出这个分支
                if (e == null) {
                    if (node == null) // speculatively create node
                        //初始化键值对,next指向null
                        node = new HashEntry<K,V>(hash, key, value, null);
                    retries = 0;
                }
                else if (key.equals(e.key))
                    retries = 0;
                else
                    e = e.next;
            }
            //超过最大自旋次数,阻塞
            else if (++retries > MAX_SCAN_RETRIES) {
                lock();
                break;
            }
            //头节点发生变化,重新遍历
            else if ((retries & 1) == 0 &&
                    (f = entryForHash(this, hash)) != first) {
                e = first = f; // re-traverse if entry changed
                retries = -1;
            }
        }
        return node;
    }

método de repetición 

 private void rehash(HashEntry<K,V> node) {
        //扩容前的旧tab数组
        HashEntry<K,V>[] oldTable = table;
        //扩容前数组长度
        int oldCapacity = oldTable.length;
        //扩容后数组长度(扩容前两倍)
        int newCapacity = oldCapacity << 1;
        //计算新的阈值
        threshold = (int)(newCapacity * loadFactor);
        //新的tab数组
        HashEntry<K,V>[] newTable =
                (HashEntry<K,V>[]) new HashEntry[newCapacity];
        //新的掩码,对该值做取模运算,计算新的下标
        int sizeMask = newCapacity - 1;
        //遍历旧的数组
        for (int i = 0; i < oldCapacity ; i++) {
            //遍历数组的每一个元素
            HashEntry<K,V> e = oldTable[i];
            if (e != null) {
                //元素e指向的下一个节点,如果存在hash冲突那么e不为空
                HashEntry<K,V> next = e.next;
                //计算元素在新数组的索引
                int idx = e.hash & sizeMask;
                // 桶中只有一个元素,把当前的e设置给新的table
                if (next == null)   //  Single node on list
                    newTable[idx] = e;
                //桶中有布置一个元素的链表
                else { // Reuse consecutive sequence at same slot
                    HashEntry<K,V> lastRun = e;
                    // idx 是当前链表的头结点 e 的新位置
                    int lastIdx = idx;
                    for (HashEntry<K,V> last = next;
                         last != null;
                         last = last.next) {
                        //k是单链表元素在新数组的位置
                        int k = last.hash & sizeMask;
                        //lastRun是最后一个扩容后不在原桶处的Entry
                        if (k != lastIdx) {
                            lastIdx = k;
                            lastRun = last;
                        }
                    }
                    //lastRun以及它后面的元素都在一个桶中
                    newTable[lastIdx] = lastRun;
                    // Clone remaining nodes
                    //遍历到lastRun即可
                    for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
                        V v = p.value;
                        int h = p.hash;
                        int k = h & sizeMask;
                        HashEntry<K,V> n = newTable[k];
                        newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
                    }
                }
            }
        }
        //处理引起扩容的那个待添加的节点
        int nodeIndex = node.hash & sizeMask; // add the new node
        node.setNext(newTable[nodeIndex]);
        newTable[nodeIndex] = node;
        //把Segment的table指向扩容后的table
        table = newTable;
    }

get (Clave de objeto) El
  elemento get no necesita ser bloqueado, y la eficiencia es alta. El segmento de segmento ubicado por la clave se usa para atravesar el elemento HashEntry de la matriz de la tabla. UNSAFE.getObjectVolatile se usa para asegurar que el valor de la última variable volátil se pueda obtener sin bloqueos. 

    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        //计算key的hash值
        int h = hash(key);
        //根据hash值计算key在哪个segment片段
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        //获取segments[u]的table数组
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            //遍历table中的HashEntry元素
            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;
                //找到相同的key,返回value
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

Talla()

  El método de tamaño se usa para calcular la cantidad de elementos almacenados en ConcurrentHashMap. Entonces, ¿es necesario bloquear el número de todos los elementos del segmento? Si no está bloqueado, puede haber otros hilos que almacenan / eliminan elementos simultáneamente en el proceso estadístico, y si está bloqueado, reducirá la eficiencia de la lectura y la escritura. ConcurrentHashMap utiliza un método de compromiso cuando se implementa. Atravesará el bloqueo tres veces sin agregar el modCount de todos los segmentos a la suma. Si la suma no cambia en comparación con el resultado transversal anterior, significa que no hay otros hilos para modificar ConcurrentHashMap en estos dos recorridos. , Devuelve la suma de los recuentos de los segmentos; si cada recorrido es diferente del anterior, bloquee y sincronice.

 public int size() {
        // Try a few times to get accurate count. On failure due to
        // continuous async changes in table, resort to locking.
        final Segment<K,V>[] segments = this.segments;
        int size;
        boolean overflow; // true if size overflows 32 bits
        long sum;         // sum of modCounts
        long last = 0L;   // previous sum
        int retries = -1; // first iteration isn't retry
        try {
            for (;;) {
                //达到RETRIES_BEFORE_LOCK,也就是三次
                if (retries++ == RETRIES_BEFORE_LOCK) {
                    for (int j = 0; j < segments.length; ++j)
                        ensureSegment(j).lock(); // force creation
                }
                sum = 0L;
                size = 0;
                overflow = false;
                for (int j = 0; j < segments.length; ++j) {
                    Segment<K,V> seg = segmentAt(segments, j);
                    //遍历计算segment的modCount和count的和
                    if (seg != null) {
                        sum += seg.modCount;
                        int c = seg.count;
                        //是否溢出int范围
                        if (c < 0 || (size += c) < 0)
                            overflow = true;
                    }
                }
                //last是上一次的sum值,相等跳出循环
                if (sum == last)
                    break;
                last = sum;
            }
        } finally {
            //解锁
            if (retries > RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    segmentAt(segments, j).unlock();
            }
        }
        return overflow ? Integer.MAX_VALUE : size;
    }

eliminar (clave de objeto)

  Llame al método remove de Segment

    public V remove(Object key) {
        int hash = hash(key);
        Segment<K,V> s = segmentForHash(hash);
        return s == null ? null : s.remove(key, hash, null);
    }
  • eliminar (clave de objeto, int hash, valor de objeto)

  Adquiera un bloqueo de sincronización y elimine el par clave-valor especificado

  final V remove(Object key, int hash, Object value) {
        //获取同步锁
        if (!tryLock())
            scanAndLock(key, hash);
        V oldValue = null;
        try {
            HashEntry<K,V>[] tab = table;
            int index = (tab.length - 1) & hash;
            HashEntry<K,V> e = entryAt(tab, index);
            //遍历链表用来保存当前链表节点的前一个节点
            HashEntry<K,V> pred = null;
            while (e != null) {
                K k;
                HashEntry<K,V> next = e.next;
                //找到key对应的键值对
                if ((k = e.key) == key ||
                        (e.hash == hash && key.equals(k))) {
                    V v = e.value;
                    //键值对的值与传入的value相等
                    if (value == null || value == v || value.equals(v)) {
                        //当前元素为头节点,把当前元素的下一个节点设为头节点
                        if (pred == null)
                            setEntryAt(tab, index, next);
                        //不是头节点,把当前链表节点的前一个节点的next指向当前节点的下一个节点
                        else
                            pred.setNext(next);
                        ++modCount;
                        --count;
                        oldValue = v;
                    }
                    break;
                }
                pred = e;
                e = next;
            }
        } finally {
            unlock();
        }
        return oldValue;
    }

 scanAndLock (clave de objeto, int hash)

  Busque la clave especificada y adquiera el bloqueo sincrónico. Cuando se ejecuta el método, es decir, debe saltar del bucle para obtener el bloqueo sincrónico. Hay dos formas de saltar del bucle: 1. El método tryLock intenta adquirir el bloqueo exclusivo con éxito 2. Intenta adquirir más del giro máximo El número de veces que MAX_SCAN_RETRIES se bloquea el hilo, cuando el hilo se despierta de la cola de espera y el bloqueo salta del bucle.

 

private void scanAndLock(Object key, int hash) {
        // similar to but simpler than scanAndLockForPut
        HashEntry<K,V> first = entryForHash(this, hash);
        HashEntry<K,V> e = first;
        int retries = -1;
        while (!tryLock()) {
            HashEntry<K,V> f;
            if (retries < 0) {
                if (e == null || key.equals(e.key))
                    retries = 0;
                else
                    e = e.next;
            }
            else if (++retries > MAX_SCAN_RETRIES) {
                lock();
                break;
            }
            else if ((retries & 1) == 0 &&
                    (f = entryForHash(this, hash)) != first) {
                e = first = f;
                retries = -1;
            }
        }
    }

esta vacio()

  Compruebe si ConcurrentHashMap está vacío. Del mismo modo, no se usa bloqueo sincrónico, a través de dos recorridos: 1. Determine si cada segmento es 0 y el recuento de cualquiera de los segmentos no es 0, luego regrese, ambos son 0 y el recuento acumulativo es la suma 2. El primer bucle El mapa puede estar vacío después de que no se haya ejecutado la ejecución. Haga otro recorrido. Si el recuento de cualquier segmento en este proceso no es 0, devuelva falso y suma menos el modCount de cada segmento. Si el programa no ha salido después de la ejecución del bucle , Compare si la suma es 0 y 0 significa que no hay ningún elemento insertado en dos comprobaciones, el mapa está vacío, de lo contrario, el mapa no está vacío.

 public boolean isEmpty() {
        //累计segment的modCount值
        long sum = 0L;
        final Segment<K,V>[] segments = this.segments;
        for (int j = 0; j < segments.length; ++j) {
            Segment<K,V> seg = segmentAt(segments, j);
            if (seg != null) {
                if (seg.count != 0)
                    return false;
                sum += seg.modCount;
            }
        }
        //再次检查
        if (sum != 0L) { // recheck unless no modifications
            for (int j = 0; j < segments.length; ++j) {
                Segment<K,V> seg = segmentAt(segments, j);
                if (seg != null) {
                    if (seg.count != 0)
                        return false;
                    sum -= seg.modCount;
                }
            }
            if (sum != 0L)
                return false;
        }
        return true;
    }

 

 

 

 

 

 

 

 

11 artículos originales publicados · Me gusta2 · Visitas 140

Supongo que te gusta

Origin blog.csdn.net/sinat_38195280/article/details/104499435
Recomendado
Clasificación