Hashmap lograr segura y eficiente hilo ConcurrentHashMap-: Java aprendizaje parte diez

breve introducción

ConcurrentHashMap en Java es un hilo-seguro y eficiente HashMap logrado . Por lo general, la participación de alta concurrencia Si queremos mapear la estructura, y que la primera idea es la misma.

Entonces me gustaría entender varios aspectos sobre ConcurrentHashMap:

1) ConcurrentHashMap JDK8 en la estructura
2) ConcurrentHashMap puso el método, el método szie
3) expansión ConcurrentHashMap
4) la diferencia entre HashMap, Hashtable, ConccurentHashMap tres
5) ConcurrentHashMap JDK8 y la diferencia JDK7

análisis de código fuente

ConcurrentHashMap en JDK8 en la estructura

En primer lugar, mirada a la estructura subyacente compuesto por:

De hecho, y 1,8 HashMap estructuralmente similar, cuando la lista de nodos excede de un valor umbral especificado , entonces, se convierte en un árbol rojo-negro , la estructura general es la misma.

Así que al final es la forma de lograr flujos seguros?
La respuesta: que abandonó el original de bloqueo de segmento del segmento, mientras que el uso de CAS + sincronizado para garantizar la seguridad de concurrencia . En cuanto a la forma, sigo a mirar el método put lógica

método put lógica

ConcurrentHashMap más comúnmente método utilizado se pone y obtener métodos, aquí sobre todo para ver los comentarios de código, la facilidad de comprensión.

 

/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
    if (key == null || value == null) throw new NullPointerException();
    //1. 计算key的hash值
    int hash = spread(key.hashCode());
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        Node<K,V> f; int n, i, fh;
        //2. 如果当前table还没有初始化先调用initTable方法将tab进行初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        //3. tab中索引为i的位置的元素为null,则直接使用CAS将值插入即可
        else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
            if (casTabAt(tab, i, null,
                         new Node<K,V>(hash, key, value, null)))
                break;                   // no lock when adding to empty bin
        }
        //4. 当前正在扩容
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            V oldVal = null;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    //5. 当前为链表,在链表中插入新的键值对
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            if (e.hash == hash &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                oldVal = e.val;
                                if (!onlyIfAbsent)
                                    e.val = value;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                pred.next = new Node<K,V>(hash, key,
                                                          value, null);
                                break;
                            }
                        }
                    }
                    // 6.当前为红黑树,将新的键值对插入到红黑树中
                    else if (f instanceof TreeBin) {
                        Node<K,V> p;
                        binCount = 2;
                        if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
                                                       value)) != null) {
                            oldVal = p.val;
                            if (!onlyIfAbsent)
                                p.val = value;
                        }
                    }
                }
            }
            // 7.插入完键值对后再根据实际大小看是否需要转换成红黑树
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    //8.对当前容量大小进行检查,如果超过了临界值(实际大小*加载因子)就需要扩容 
    addCount(1L, binCount);
    return null;
}

Por favor, lea los comentarios de código, hay un entendimiento general, entonces aprendemos acerca con más detalle:

Este proceso claramente puesto sobre la mesa para que la corriente de venta sin condiciones de la circulación hasta que tenga éxito , se puede dividir en el siguiente proceso de seis etapas que se describe por:

1, se determina Node [] matriz se inicializa, no es initialize operación
2, el índice de coordenadas de posicionamiento array de hash , si un nodo de nodo, si no se usa CAS add (lista de nodos de la cabeza), el proceso pasa a la siguiente adición falló ciclo.
3, la parte interna es la expansión, que ayudó a la expansión.
4, si f! = Null, a continuación, el uso de bloqueo sincronizado f elemento (lista / elemento de cabeza árbol negro). Si el nodo (estructura de la cadena) para añadir la lista se ejecuta la operación; si el TreeNode (estructura de árbol) para añadir se realiza la operación de árbol.
5, se juzga longitud de la cadena 8 ha alcanzado un valor crítico (valor predeterminado), cuando el valor de nodo supera la necesidad de convertir la lista para una estructura de árbol .
6, cuando agregado con éxito invoca el método de tamaño estadística addCount (), y comprobar si necesita la expansión

1.spread (clave, hashCode ())

Este método de acción pesada Hash , Hash a reducir los conflictos

 

static final int spread(int h) {
    return (h ^ (h >>> 16)) & HASH_BITS;
}

El método es principalmente un mínimo de 16 clave hashCode se encuentra en la alta operación XOR de 16 bits , de modo que el valor hash no sólo puede ser dispersado uniformemente reducir la probabilidad de colisión de hash, sólo se utiliza la operación adicional XOR, y la sobrecarga de rendimiento capaz de combinar.

método 2.initTable

El papel principal será inicializado pestaña

 

private final Node<K,V>[] initTable() {
    Node<K,V>[] tab; int sc;
    while ((tab = table) == null || tab.length == 0) {
        if ((sc = sizeCtl) < 0)
            // 1. 保证只有一个线程正在进行初始化操作
            Thread.yield(); // lost initialization race; just spin
        else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
            try {
                if ((tab = table) == null || tab.length == 0) {
                    // 2. 得出数组的大小
                    int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
                    @SuppressWarnings("unchecked")
                    // 3. 这里才真正的初始化数组
                    Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
                    table = tab = nt;
                    // 4. 计算数组中可用的大小:实际大小n*0.75(加载因子)
                    sc = n - (n >>> 2);
                }
            } finally {
                sizeCtl = sc;
            }
            break;
        }
    }
    return tab;
}

Para asegurarse de que ha inicializado correctamente, en el paso 1 primero determinará si se lleva a través de, si el hilo actual se está inicializando tiene un valor que es sizeCtl se convierte en -1 , esta vez en el otro hilo Si se determina para ser verdad para invocar Thread.yield () dejar que el intervalo de tiempo de CPU. Al ser inicializado hilo llama cambio U.compareAndSwapInt método sizeCtl que se está inicializando el estado 1. Otras cosas también hay que señalar que, en el cuarto paso más allá calcula el tamaño de la matriz es la matriz disponible en el tamaño real del factor de carga es de 0,75 multiplicado por n. Aquí se puede ver cómo el recuento multiplicado por 0,75, y 0,75 para el trimestre en tercer lugar, donde n - (n >>> 2) no es exactamente la N- (1/4) = las N- (3/4) del n- , es muy interesante :). Si no se seleccionan parámetros de constructor, aquí se utilizará en nuevo nodo es el tamaño predeterminado cuando el DEFAULT_CAPACITY array (16) y, a continuación, multiplicado por el factor de carga es de 0,75 12, es decir el tamaño disponible de la matriz 12.

3.CAS operaciones críticas

Tabat () Este método se utiliza para obtener el elemento de nodo de la tabla de índice de matriz i .
casTabAt () mediante el funcionamiento CAS tabla de establecimiento elemento de índice de matriz i
setTabAt () se utiliza para establecer el índice de la tabla en el elemento i array

expansión 4.ConcurrentHashMap

Mediante la determinación de un valor de dispersión del nodo no es igual a -1 (desplazado), código (fh = f.hash) == movido, Mapa se describe expansión . Entonces ayudado Mapa de expansión. A la velocidad.
¿Cómo ayudar a la expansión de la misma? Para lograr ese aspecto helpTransfer métodos.

 

/**
 * Helps transfer if a resize is in progress.
 */
final Node<K,V>[] helpTransfer(Node<K,V>[] tab, Node<K,V> f) {
    Node<K,V>[] nextTab; int sc;
    // 如果 table 不是空 且 node 节点是转移类型,数据检验
    // 且 node 节点的 nextTable(新 table) 不是空,同样也是数据校验
    // 尝试帮助扩容
    if (tab != null && (f instanceof ForwardingNode) &&
        (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
        // 根据 length 得到一个标识符号
        int rs = resizeStamp(tab.length);
        // 如果 nextTab 没有被并发修改 且 tab 也没有被并发修改
        // 且 sizeCtl  < 0 (说明还在扩容)
        while (nextTab == nextTable && table == tab &&
               (sc = sizeCtl) < 0) {
            // 如果 sizeCtl 无符号右移  16 不等于 rs ( sc前 16 位如果不等于标识符,则标识符变化了)
            // 或者 sizeCtl == rs + 1  (扩容结束了,不再有线程进行扩容)(默认第一个线程设置 sc ==rs 左移 16 位 + 2,当第一个线程结束扩容了,就会将 sc 减一。这个时候,sc 就等于 rs + 1)
            // 或者 sizeCtl == rs + 65535  (如果达到最大帮助线程的数量,即 65535)
            // 或者转移下标正在调整 (扩容结束)
            // 结束循环,返回 table
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || transferIndex <= 0)
                break;
            // 如果以上都不是, 将 sizeCtl + 1, (表示增加了一个线程帮助其扩容)
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
                // 进行转移
                transfer(tab, nextTab);
                // 结束循环
                break;
            }
        }
        return nextTab;
    }
    return table;
}

La lógica básica es en los comentarios de código, donde la transferencia de llave (), luego continuamos mirada en profundidad

 

private final void transfer(Node<K,V>[] tab, Node<K,V>[] nextTab) {
        int n = tab.length, stride;
        // 每核处理的量小于16,则强制赋值16
        if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
            stride = MIN_TRANSFER_STRIDE; // subdivide range
        if (nextTab == null) {            // initiating
            try {
                @SuppressWarnings("unchecked")
                Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];        //构建一个nextTable对象,其容量为原来容量的两倍
                nextTab = nt;
            } catch (Throwable ex) {      // try to cope with OOME
                sizeCtl = Integer.MAX_VALUE;
                return;
            }
            nextTable = nextTab;
            transferIndex = n;
        }
        int nextn = nextTab.length;
        // 连接点指针,用于标志位(fwd的hash值为-1,fwd.nextTable=nextTab)
        ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
        // 当advance == true时,表明该节点已经处理过了
        boolean advance = true;
        boolean finishing = false; // to ensure sweep before committing nextTab
        for (int i = 0, bound = 0;;) {
            Node<K,V> f; int fh;
            // 控制 --i ,遍历原hash表中的节点
            while (advance) {
                int nextIndex, nextBound;
                if (--i >= bound || finishing)
                    advance = false;
                else if ((nextIndex = transferIndex) <= 0) {
                    i = -1;
                    advance = false;
                }
                // 用CAS计算得到的transferIndex
                else if (U.compareAndSwapInt
                        (this, TRANSFERINDEX, nextIndex,
                                nextBound = (nextIndex > stride ?
                                        nextIndex - stride : 0))) {
                    bound = nextBound;
                    i = nextIndex - 1;
                    advance = false;
                }
            }
            if (i < 0 || i >= n || i + n >= nextn) {
                int sc;
                // 已经完成所有节点复制了
                if (finishing) {
                    nextTable = null;
                    table = nextTab;        // table 指向nextTable
                    sizeCtl = (n << 1) - (n >>> 1);     // sizeCtl阈值为原来的1.5倍
                    return;     // 跳出死循环,
                }
                // CAS 更扩容阈值,在这里面sizectl值减一,说明新加入一个线程参与到扩容操作
                if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
                    if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
                        return;
                    finishing = advance = true;
                    i = n; // recheck before commit
                }
            }
            // 遍历的节点为null,则放入到ForwardingNode 指针节点
            else if ((f = tabAt(tab, i)) == null)
                advance = casTabAt(tab, i, null, fwd);
            // f.hash == -1 表示遍历到了ForwardingNode节点,意味着该节点已经处理过了
            // 这里是控制并发扩容的核心
            else if ((fh = f.hash) == MOVED)
                advance = true; // already processed
            else {
                // 节点加锁
                synchronized (f) {
                    // 节点复制工作
                    if (tabAt(tab, i) == f) {
                        Node<K,V> ln, hn;
                        // fh >= 0 ,表示为链表节点
                        if (fh >= 0) {
                            // 构造两个链表  一个是原链表  另一个是原链表的反序排列
                            int runBit = fh & n;
                            Node<K,V> lastRun = f;
                            for (Node<K,V> p = f.next; p != null; p = p.next) {
                                int b = p.hash & n;
                                if (b != runBit) {
                                    runBit = b;
                                    lastRun = p;
                                }
                            }
                            if (runBit == 0) {
                                ln = lastRun;
                                hn = null;
                            }
                            else {
                                hn = lastRun;
                                ln = null;
                            }
                            for (Node<K,V> p = f; p != lastRun; p = p.next) {
                                int ph = p.hash; K pk = p.key; V pv = p.val;
                                if ((ph & n) == 0)
                                    ln = new Node<K,V>(ph, pk, pv, ln);
                                else
                                    hn = new Node<K,V>(ph, pk, pv, hn);
                            }
                            // 在nextTable i 位置处插上链表
                            setTabAt(nextTab, i, ln);
                            // 在nextTable i + n 位置处插上链表
                            setTabAt(nextTab, i + n, hn);
                            // 在table i 位置处插上ForwardingNode 表示该节点已经处理过了
                            setTabAt(tab, i, fwd);
                            // advance = true 可以执行--i动作,遍历节点
                            advance = true;
                        }
                        // 如果是TreeBin,则按照红黑树进行处理,处理逻辑与上面一致
                        else if (f instanceof TreeBin) {
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> lo = null, loTail = null;
                            TreeNode<K,V> hi = null, hiTail = null;
                            int lc = 0, hc = 0;
                            for (Node<K,V> e = t.first; e != null; e = e.next) {
                                int h = e.hash;
                                TreeNode<K,V> p = new TreeNode<K,V>
                                        (h, e.key, e.val, null, null);
                                if ((h & n) == 0) {
                                    if ((p.prev = loTail) == null)
                                        lo = p;
                                    else
                                        loTail.next = p;
                                    loTail = p;
                                    ++lc;
                                }
                                else {
                                    if ((p.prev = hiTail) == null)
                                        hi = p;
                                    else
                                        hiTail.next = p;
                                    hiTail = p;
                                    ++hc;
                                }
                            }
                            // 扩容后树节点个数若<=6,将树转链表
                            ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
                                    (hc != 0) ? new TreeBin<K,V>(lo) : t;
                            hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
                                    (lc != 0) ? new TreeBin<K,V>(hi) : t;
                            setTabAt(nextTab, i, ln);
                            setTabAt(nextTab, i + n, hn);
                            setTabAt(tab, i, fwd);
                            advance = true;
                        }
                    }
                }
            }
        }
    }

proceso de expansión es un poco complicado, puede ver las notas de cabeza. Aquí principalmente relacionados con la expansión de la multi-hilo, papel ForwardingNode es apoyar la expansión de las operaciones, el nodo de procesado y un conjunto ForwardingNode nodo vacío, cuando se procesan múltiples hilos concurrentes a través de ForwardingNode dijo que había atravesado, se recorre la parte posterior, parte inferior multihilo es un proceso de expansión de la cooperación:

5.treeifyBin () procesa la lista de las revoluciones árbol rojo-negro

La lógica básica en los comentarios de código.

 

private final void treeifyBin(Node<K,V>[] tab, int index) {
    Node<K,V> b; int n, sc;
    if (tab != null) {
        //如果整个table的数量小于64,就扩容至原来的一倍,不转红黑树了
        //因为这个阈值扩容可以减少hash冲突,不必要去转红黑树
        if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
            tryPresize(n << 1);
        else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
            synchronized (b) {
                if (tabAt(tab, index) == b) {
                    TreeNode<K,V> hd = null, tl = null;
                    for (Node<K,V> e = b; e != null; e = e.next) {
                        //封装成TreeNode
                        TreeNode<K,V> p =
                            new TreeNode<K,V>(e.hash, e.key, e.val,
                                              null, null);
                        if ((p.prev = tl) == null)
                            hd = p;
                        else
                            tl.next = p;
                        tl = p;
                    }
                    //通过TreeBin对象对TreeNode转换成红黑树
                    setTabAt(tab, index, new TreeBin<K,V>(hd));
                }
            }
        }
    }
}

método addCount () de calcular el tamaño de ConcurrentHashMap

 

private final void addCount(long x, int check) {
    CounterCell[] as; long b, s;
    //更新baseCount,table的数量,counterCells表示元素个数的变化
    if ((as = counterCells) != null ||
        !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
        CounterCell a; long v; int m;
        boolean uncontended = true;
        //如果多个线程都在执行,则CAS失败,执行fullAddCount,全部加入count
        if (as == null || (m = as.length - 1) < 0 ||
            (a = as[ThreadLocalRandom.getProbe() & m]) == null ||
            !(uncontended =
              U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
            fullAddCount(x, uncontended);
            return;
        }
        if (check <= 1)
            return;
        s = sumCount();
    }
     //check>=0表示需要进行扩容操作
    if (check >= 0) {
        Node<K,V>[] tab, nt; int n, sc;
        while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
               (n = tab.length) < MAXIMUM_CAPACITY) {
            int rs = resizeStamp(n);
            if (sc < 0) {
                if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                    sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                    transferIndex <= 0)
                    break;
                if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                    transfer(tab, nt);
            }
            //当前线程发起库哦哦让操作,nextTable=null
            else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                         (rs << RESIZE_STAMP_SHIFT) + 2))
                transfer(tab, null);
            s = sumCount();
        }
    }
}

proceso de venta ha sido analizado más, es posible que se utiliza en el procesamiento simultáneo es el bloqueo optimista, cuando hay conflictos han llegado a procesamiento concurrente, y los pasos del proceso son claras, pero los detalles del diseño es muy complicado, después de todo roscado múltiples la escena también complicado.

método get

ConcurrentHashMap el proceso de operación de obtención es muy simple, se puede describir en tres etapas:

1. valor hash Calcular, para localizar la posición de la tabla de índice, si el primer nodo está en línea con las devoluciones.
2. Si usted tiene la expansión en el tiempo, va a llamar signos nodo ForwardingNode el método de búsqueda es la expansión y la mirada para el nodo, los rendimientos de los partidos.
3. El anteriormente no cumplen, entonces es hacia abajo para atravesar el nodo, los rendimientos de los partidos, de lo contrario devuelve null definitiva

 

public V get(Object key) {
    Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
    int h = spread(key.hashCode()); //计算两次hash
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (e = tabAt(tab, (n - 1) & h)) != null) {//读取首节点的Node元素
        if ((eh = e.hash) == h) { //如果该节点就是首节点就返回
            if ((ek = e.key) == key || (ek != null && key.equals(ek)))
                return e.val;
        }
        //hash值为负值表示正在扩容,这个时候查的是ForwardingNode的find方法来定位到nextTable来
        //查找,查找到就返回
        else if (eh < 0)
            return (p = e.find(h, key)) != null ? p.val : null;
        while ((e = e.next) != null) {//既不是首节点也不是ForwardingNode,那就往下遍历
            if (e.hash == h &&
                ((ek = e.key) == key || (ek != null && key.equals(ek))))
                return e.val;
        }
    }
    return null;
}

szie lógico Método

Para el cálculo del tamaño, la expansión y el método addCount () habría sido tratado, puede tomar la función de la nota de venta, que tendrá la función addCount () , ya calculado, y luego, cuando el tamaño directamente a usted.

 

public int size() {
    long n = sumCount();
    return ((n < 0L) ? 0 :
            (n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
            (int)n);
}
final long sumCount() {
    CounterCell[] as = counterCells; CounterCell a; //变化的数量
    long sum = baseCount;
    if (as != null) {
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

La diferencia entre HashMap, Hashtable, ConccurentHashMap tres

HashMap thread-safe, una matriz + + lista rojo-negro árbol
Hashtable thread-safe, bloquee todo el objeto, una + lista de arreglo
ConccurentHashMap thread-safe, bloqueo de sincronización CAS +, una matriz + + lista de árbol rojo-negro
HashMap de clave, el valor puede ser nulo, el otro no dos.

Diferencia en el JDK1.7 y JDK1.8

JDK1.8 importantes mejoras en el diseño son los siguientes:

1, sin usar segmento nodo empleado, para lograr un nodo granularidad de bloqueo reducción bloqueado .
2, en el proceso de diseño de un estado movido al cambiar el tamaño de la rosca 2 datos también ponen, hilo 2 le ayudará a cambiar el tamaño.
3, usando 3 nodo operación CAS para asegurar algunos de los átomos de la operación , en lugar de la cerradura de esta manera.
4, diferentes valores sizeCtl para representar diferentes significados, actúa para controlar.
En lugar de utilizar ReentrantLock sincronizado

resumen

ConcurrentHashMap principio básico se resume aquí, donde no faltan puede dejar un mensaje para decirme que ha.

Publicados 377 artículos originales · ganado elogios 145 · vistas 210 000 +

Supongo que te gusta

Origin blog.csdn.net/Windgs_YF/article/details/104298838
Recomendado
Clasificación