ConcurrentHashMap在jdk1.8的详解

源码为jdk1.8

Java7与Java8中的ConcurrentHashMap:

在ConcurrentHashMap中主要通过锁分段技术实现上述目标。

在Java7中,ConcurrentHashMap由Segment数组结构和HashEntry数组组成。

Segment(分段锁) ,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。 ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。

在Java8中,ConcurrentHashMap去除了Segment分段锁的数据结构,主要是基于CAS操作保证保证数据的获取以及使用synchronized关键字对相应数据段加锁实现了主要功能,这进一步提高了并发性。同时同时为了提高哈希碰撞下的寻址性能,Java 8在链表长度超过一定阈值时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(long(N)))。

ConcurrentHashMap介绍

ConcurrentHashMap 是concurrent包下的一个集合类。它是线程安全的哈希表。它是通过“分段锁”来实现多线程下的安全问题。它将哈希表分成了不同的段内使用了可重入锁(ReentrantLock ),不同线程只在一个段内存在线程的竞争。它不会对整个哈希表加锁。

public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable {

它继承了AbstractMap类,实现了ConcurrentMap,Serializable接口

ConcurrentHashMap具有的属性

    //默认容量大小
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    //默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //默认并发数量(segments数组的大小)
    static final int DEFAULT_CONCURRENCY_LEVEL = 16;
    //最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //最大并发数量
    static final int MAX_SEGMENTS = 1 << 16;
    //节点长度的阀值为8
     static final int TREEIFY_THRESHOLD = 8;

​ 目前ConcurrentHashMap采用Node类作为基本的存储单元,每个键值对(key-value)都存储在一个Node中。同时Node也有一些子类,TreeNodes用于树结构中(当链表长度大于一定阈值时将链表转化为红黑树);

   static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;

        Node(int hash, K key, V val, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.val = val;
            this.next = next;
        }

        public final K getKey()       { return key; }
        public final V getValue()     { return val; }
        public final int hashCode()   { return key.hashCode() ^ val.hashCode(); }
        public final String toString(){ return key + "=" + val; }
        public final V setValue(V value) {
            throw new UnsupportedOperationException();
        }

        public final boolean equals(Object o) {...};
        /**
         * Virtualized support for map.get(); overridden in subclasses.
         */
        Node<K,V> find(int h, Object k) {...}
    }

ConcurrentHashMap中的原子操作

在ConcurrentHashMap中通过原子操作查找元素、替换元素和设置元素。这些原子操作起着非常关键的作用。

 static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
        return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    }

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

ConcurrentHashMap的put方法

如果key或者value为null,则抛出空指针异常;

如果table为null或者table的长度为0,则初始化table,调用initTable()方法。

计算当前键值的索引位置,如果Hash表中当前节点为null,则将元素直接插入。(注意,这里使用的就是前面锁说的CAS操作)

如果当前位置的节点元素的hash值为-1,说明这是一个ForwaringNodes节点,即正在进行扩容。那么当前线程加入扩容。

当前节点不为null,对当前节点加锁,将元素插入到当前节点。在Java8中,当节点长度大于阀值时,就将节点转为树的结构。

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();
        //计算传入key的hash值,
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable(); //初始化哈希表
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                //CAS操作插入
                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;
                //*重点:锁的是当前的Node节点 而不是整个hash表
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek;
                                //当key相同且它的hash值相同时,替换当前元素的值
                                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;
    }

ConcurrentHashMap与HashMap、HashTable的区别

1.ConcurrentHashMap和HashMap是2k扩容,HashTable扩容:2k+1;
2.ConcurrentHashMap 、HashTable线程安全;HashMap线程不安全;
3.ConcurrentHashMap 、HashTable不允许值为null,值为null时抛出异常,
HashMap允许值为null;
4.ConcurrentHashMap在jdk1.8中底层用的是synchronized+CAS+HashEntry+红黑树。
5.HashTable所有方法都加锁synchronized。当一个线程来访问时候,会锁定整个map表,其他线程则不 能去访问该map表;
ConcurrentHashMap采用了CAS+同步锁Synchronized对链表中Node<k,V>节点进行锁定;当一个线程访问时候,会锁住该Node节点,而其他线程过来若访问其他节点时候不影响,可以正常访问;


版权声明:本文为qq_39602004原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。