jdk1.8 ConcurrentHashMap

来源:互联网 发布:电气硬件 软件是什么 编辑:程序博客网 时间:2024/05/27 09:46

ConcurrentHashMap

ConcurrentHashMap的线程安全指的是,它的每个方法单独调用(即原子操作)都是线程安全的,但是代码总体的互斥性并不受控制

JDK1.8 ConcurrentHashMap与1.7的区别

1.  取消segments字段,直接采用transient volatile HashEntry<K,V>[]table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,进一步减少并发冲突的概率。2.  将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。3.  size()方法效率更高,采用了中间变量进行存储,不需要遍历,但是不保证线程安全即准确数据。

Node(jdk1.8):

    static class Node<K,V> implements Map.Entry<K,V> {        final int hash;        final K key;        volatile V val;        volatile Node<K,V> next;        /**         * Virtualized support for map.get(); overridden in subclasses.         */        Node<K,V> find(int h, Object k) {            Node<K,V> e = this;            if (k != null) {                do {                    K ek;                    if (e.hash == h &&                        ((ek = e.key) == k || (ek != null && k.equals(ek))))                        return e;                } while ((e = e.next) != null);            }            return null;        }    }

key与hash是不可变的,value和next是volatile而且可变的。

3个常用的原子操作

    @SuppressWarnings("unchecked") // ASHIFT等均为private static final      static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) { // 获取索引i处Node          return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);      }      // 利用CAS算法设置i位置上的Node节点(将c和table[i]比较,相同则插入v)。      static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,                                          Node<K,V> c, Node<K,V> v) {          return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);      }      // 设置节点位置的值,仅在上锁区被调用      static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {          U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);      }  

get(key)

    // jdk1.6 readValueUnderLock有加锁的操作,1.6的jdk采用了乐观锁的方式处理了get方法,在get的时候put方法正在new对象,而此时value并未赋值,这时判断为空则加锁访问    V get(Object key, int hash) {         if (count != 0) { // read-volatile             HashEntry<K,V> e = getFirst(hash);             while (e != null) {                 if (e.hash == hash && key.equals(e.key)) {                     V v = e.value;                     if (v != null)                          return v;                      return readValueUnderLock(e); // recheck                 }                 e = e.next;             }         }         return null;     }
    // jdk1.8没有加锁    public V get(Object key) {        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;        int h = spread(key.hashCode());        if ((tab = table) != null && (n = tab.length) > 0 &&            (e = tabAt(tab, (n - 1) & h)) != null) {            if ((eh = e.hash) == h) {                if ((ek = e.key) == key || (ek != null && key.equals(ek)))                    return e.val;            }            else if (eh < 0)                return (p = e.find(h, key)) != null ? p.val : null;            while ((e = e.next) != null) {                if (e.hash == h &&                    ((ek = e.key) == key || (ek != null && key.equals(ek))))                    return e.val;            }        }        return null;    }

get方法不带锁,但是put和remove等等方法需要加锁,说明了get方法和put方法是可以同时执行的,但是不保证强一致性。
根据api的描述:检索会影响最近完成的 更新操作的结果,所谓完成的,就是从put方法返回了。

1.7和1.8并没有判断value=null的情况,无论是1.6还是1.7的实现,实际上都是一种乐观的方式,而乐观的方式带来的是性能上的提升,但同时也带来数据的弱一致性,如果你的业务是强一致性的业务,可能就要考虑另外的解决办法(用Collections包装或者像jdk6中一样二次加锁获取)

put(key,value)

    public V put(K key, V value) {        return putVal(key, value, false);    }    /** Implementation for put and putIfAbsent */    final V putVal(K key, V value, boolean onlyIfAbsent) {        if (key == null || value == null) throw new NullPointerException();        int hash = spread(key.hashCode());        int binCount = 0;        for (Node<K,V>[] tab = table;;) {            Node<K,V> f; int n, i, fh;            // 如果table为空,初始化;否则,根据hash值计算得到数组索引i,如果tab[i]为空,直接新建节点Node即可。注:tab[i]实质为链表或者红黑树的首节点。            if (tab == null || (n = tab.length) == 0)                tab = initTable();            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            }            else if ((fh = f.hash) == MOVED)                tab = helpTransfer(tab, f);            else {                V oldVal = null;                // 针对segment加锁                synchronized (f) {                    if (tabAt(tab, i) == f) {                        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;                                }                            }                        }                        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;                            }                        }                    }                }                if (binCount != 0) {                    // 处理冲突                    if (binCount >= TREEIFY_THRESHOLD)                        treeifyBin(tab, i);                    if (oldVal != null)                        return oldVal;                    break;                }            }        }        addCount(1L, binCount);        return null;    }

remove(key)

public V remove(Object key) {        return replaceNode(key, null, null);    }    /**     * Implementation for the four public remove/replace methods:     * Replaces node value with v, conditional upon match of cv if     * non-null.  If resulting value is null, delete.     */    final V replaceNode(Object key, V value, Object cv) {        int hash = spread(key.hashCode());        for (Node<K,V>[] tab = table;;) {            Node<K,V> f; int n, i, fh;            if (tab == null || (n = tab.length) == 0 ||                (f = tabAt(tab, i = (n - 1) & hash)) == null)                break;            else if ((fh = f.hash) == MOVED)                tab = helpTransfer(tab, f);            else {                V oldVal = null;                boolean validated = false;                // 和put一样上锁                synchronized (f) {                    if (tabAt(tab, i) == f) {                        if (fh >= 0) {                            validated = true;                            for (Node<K,V> e = f, pred = null;;) {                                K ek;                                if (e.hash == hash &&                                    ((ek = e.key) == key ||                                     (ek != null && key.equals(ek)))) {                                    V ev = e.val;                                    if (cv == null || cv == ev ||                                        (ev != null && cv.equals(ev))) {                                        oldVal = ev;                                        if (value != null)                                            e.val = value;                                        else if (pred != null)                                            pred.next = e.next;                                        else                                            setTabAt(tab, i, e.next);                                    }                                    break;                                }                                pred = e;                                if ((e = e.next) == null)                                    break;                            }                        }                        else if (f instanceof TreeBin) {                            validated = true;                            TreeBin<K,V> t = (TreeBin<K,V>)f;                            TreeNode<K,V> r, p;                            if ((r = t.root) != null &&                                (p = r.findTreeNode(hash, key, null)) != null) {                                V pv = p.val;                                if (cv == null || cv == pv ||                                    (pv != null && cv.equals(pv))) {                                    oldVal = pv;                                    if (value != null)                                        p.val = value;                                    else if (t.removeTreeNode(p))                                        setTabAt(tab, i, untreeify(t.first));                                }                            }                        }                    }                }                if (validated) {                    if (oldVal != null) {                        if (value == null)                            addCount(-1L, -1);                        return oldVal;                    }                    break;                }            }        }        return null;    }

迭代器:

static final class EntryIterator<K,V> extends BaseIterator<K,V>        implements Iterator<Map.Entry<K,V>> {        EntryIterator(Node<K,V>[] tab, int index, int size, int limit,                      ConcurrentHashMap<K,V> map) {            super(tab, index, size, limit, map);        }        public final Map.Entry<K,V> next() {            Node<K,V> p;            if ((p = next) == null)                throw new NoSuchElementException();            K k = p.key;            V v = p.val;            lastReturned = p;            advance();            return new MapEntry<K,V>(k, v, map);        }    }    final Node<K,V> advance() {            Node<K,V> e;            if ((e = next) != null)                e = e.next;            for (;;) {                Node<K,V>[] t; int i, n;  // must use locals in checks                if (e != null)                    return next = e;                if (baseIndex >= baseLimit || (t = tab) == null ||                    (n = t.length) <= (i = index) || i < 0)                    return next = null;                if ((e = tabAt(t, i)) != null && e.hash < 0) {                    if (e instanceof ForwardingNode) {                        tab = ((ForwardingNode<K,V>)e).nextTable;                        e = null;                        pushState(t, i, n);                        continue;                    }                    else if (e instanceof TreeBin)                        e = ((TreeBin<K,V>)e).first;                    else                        e = null;                }                if (stack != null)                    recoverState(n);                else if ((index = i + baseSize) >= n)                    index = ++baseIndex; // visit upper slots if present            }        }

没有加锁操作,说明更新的操作能够直接影响迭代过程的数据,但是不能保证强一致性
单独的读写操作(get,put,putIfAbsent…)是完全线程安全且一致的,但是迭代时候则不是强一致的,迭代所遍历的不是迭代时刻的快照,而是各个segement的真是数据,所以迭代期间如有数据发生变更,如果变更的是已经遍历的segement则迭代过程不再感知这个变化,但如果变化发生在未遍历的segement,则本次迭代会感知到这个元素。

clear()

public void clear() {        long delta = 0L; // negative number of deletions        int i = 0;        Node<K,V>[] tab = table;        while (tab != null && i < tab.length) {            int fh;            Node<K,V> f = tabAt(tab, i);            if (f == null)                ++i;            else if ((fh = f.hash) == MOVED) {                tab = helpTransfer(tab, f);                i = 0; // restart            }            else {                // 同步                synchronized (f) {                    if (tabAt(tab, i) == f) {                        Node<K,V> p = (fh >= 0 ? f :                                       (f instanceof TreeBin) ?                                       ((TreeBin<K,V>)f).first : null);                        while (p != null) {                            --delta;                            p = p.next;                        }                        setTabAt(tab, i++, null);                    }                }            }        }        if (delta != 0L)            addCount(delta, -1);    }

size()

    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;    }

没有加锁,无法保证强一致性

扩容

当ConcurrentHashMap容量不足的时候,需要对table进行扩容。这个方法的基本思想跟HashMap是很像的,但是由于它是支持并发扩容的,所以要复杂的多。原因是它支持多线程进行扩容操作,而并没有加锁。我想这样做的目的不仅仅是为了满足concurrent的要求,而是希望利用并发处理去减少扩容带来的时间影响。因为在扩容的时候,总是会涉及到从一个“数组”到另一个“数组”拷贝的操作,如果这个操作能够并发进行,那真真是极好的了。

整个扩容操作分为两个部分

第一部分是构建一个nextTable,它的容量是原来的两倍,这个操作是单线程完成的。这个单线程的保证是通过RESIZE_STAMP_SHIFT这个常量经过一次运算来保证的,这个地方在后面会有提到;第二个部分就是将原来table中的元素复制到nextTable中,这里允许多线程进行操作。

总结:

concurrentHashMap在要求强一致性下无法替代HashTable,其弱一致性是强一致性和效率的平衡。
参考1
参考2

0 0