ConcurrentHashMap源码阅读(JDK1.7+JDK1.8)

一.JDK1.8实现

关于UnSafe

    JDK中有一个类Unsafe,它提供了硬件级别的原子操作。JDK API文档也没有提供任何关于这个类的方法的解释。
    从描述可以了解到Unsafe提供了硬件级别的操作,比如说获取某个属性在内存中的位置,比如说修改对象的字段值,即使它是私有的。

数据结构

和HashMap采用类似的数据结构,主要实现使用了Unsafe方法提供的原子操作和sychronized关键字实现
在这里插入图片描述

1.原子操作:tabAt

用来返回节点数组的指定位置的节点的原子操作

 @SuppressWarnings("unchecked")
    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);
    }

2.原子操作:casTabAt

cas原子操作,在指定位置设定值

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

3.原子操作:setTabAt

原子操作,在指定位置设定值

static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
        U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    }

4.get

get操作支持并发

   public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        //1.计算哈希值
        int h = spread(key.hashCode());
        //2.判断table不为空,长度不为0,第一个节点不为空
        if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) {
            //3.如果第一个节点的哈希值相等并且值也相等,直接返回.
            if ((eh = e.hash) == h) {
                if ((ek = e.key) == key || (ek != null && key.equals(ek))){
                    return e.val;
                }
            }
            //4.如果哈希值小于0,从当前节点一直查找,找到返回对应值或者null
            else if (eh < 0){
                return (p = e.find(h, key)) != null ? p.val : null;
            }
            //5.如果上述两种情况不满足,则遍历链表进行查找.
            while ((e = e.next) != null) {
                if (e.hash == h &&
                    ((ek = e.key) == key || (ek != null && key.equals(ek))))
                    return e.val;
            }
        }
        return null;
        //这边看来,get操作是支持并发的.
    }

5.put

put方法这边实现

  • 利用UnSafe的CAS方法添加第一个节点(如果为空)
  • 如果检测到哈希值为MOVED,会启动帮助扩容
  • 设值节点(第一个节点不为空)操作通过sychronized方法保证同步.
    图示:
    在这里插入图片描述
	public V put(K key, V value) {
        return putVal(key, value, false);
    }

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        //1.不允许key或者value为null的情况
        if (key == null || value == null) {
            throw new NullPointerException();
        }
        //2.计算哈希值
        int hash = spread(key.hashCode());
        int binCount = 0;
        //3.进入循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //4.初始化table
            if (tab == null || (n = tab.length) == 0){
                tab = initTable();
            }
            //5.如果第一个节点为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
            }
            //6.如果检测出当前哈希值是MOVED,说明此时正在扩容,这边会帮助扩容
            else if ((fh = f.hash) == MOVED){
                tab = helpTransfer(tab, f);
            }
            //7.进入设值阶段,同步操作
            else {
                V oldVal = null;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            //8.链表的处理--这边直接进行赋值
                            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;
                                }
                            }
                        }
                        //9.红黑树结构的操作
                        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;
                            }
                        }
                    }
                }
                //10.判断长度是否超过,超过则转换为树结构,否则直接返回
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        //11.计数---不是很理解为什么这么做
        addCount(1L, binCount);
        return null;
    }

6.clear

这边有一步,检测到MOVED,帮助扩容,这边我的理解是需要清空所有扩容的tab,不然会有内存隐患

 public void clear() {
        long delta = 0L; // negative number of deletions
        int i = 0;
        Node<K,V>[] tab = table;
        //1.循环直到所有节点为空
        while (tab != null && i < tab.length) {
            int fh;
            Node<K,V> f = tabAt(tab, i);
            //2.略过空桶
            if (f == null)
                ++i;
            //3.帮助扩容?
            else if ((fh = f.hash) == MOVED) {
                tab = helpTransfer(tab, f);
                i = 0; // restart
            }
            //4.同步块处理,清空当前桶所有节点
            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);
    }

7.remove

remove这边直接调用了replaceNode方法
这边也有一步是帮助扩容的操作

 final V replaceNode(Object key, V value, Object cv) {
        int hash = spread(key.hashCode());
        //1.进入循环
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            //2.头结点为空,说明没有元素,直接退出循环,返回
            if (tab == null || (n = tab.length) == 0 ||
                (f = tabAt(tab, i = (n - 1) & hash)) == null)
                break;
            //3.如果在做扩容操作,则帮助扩容
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else {
                V oldVal = null;
                boolean validated = false;
                //4.锁定该节点
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            validated = true;
                            //5.循环链表查找,找到对应元素设值,否则继续往下
                            for (Node<K,V> e = f, pred = null;;) {
                                K ek;
                                //6.找到对应节点
                                if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) {
                                    V ev = e.val;
                                    //7.设值操作
                                    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;
                                //8.达到最后节点,退出
                                if ((e = e.next) == null)
                                    break;
                            }
                        }
                        //9.红黑树的替换处理
                        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;
    }

二.JDK1.7实现

该部分参考:ConcurrentHashMap1.7源码分析

数据结构

jdk1.7的concurrentHashMap使用一组segment对象存储对应的HashEntry数组,每个HashEntry对象上又挂着一组链表,代表真正的key-value数据对象。
在这里插入图片描述

1.Segment

下面可以看出segment是继承了ReentrantLock ,猜测这边的同步操作依赖于锁

    static final class Segment<K,V> extends ReentrantLock implements Serializable {
        //table数组
        transient volatile HashEntry<K,V>[] table;
        transient int count;      
        transient int modCount;
		//临界值
        transient int threshold;
        //负载因子
        final float loadFactor;
}

2.HashEntry

HashEntry节点和HashMap的Entry节点类似,不过这边对value和next节点加了volatitle关键字

    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        volatile V value;
        volatile HashEntry<K,V> next;
        }

3.get

 public V get(Object key) {
        Segment<K,V> s;
        HashEntry<K,V>[] tab;
        //1.获取key对应hash值
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        //2.通过Unsafe中的getObjectVolatile方法进行volatile语义的读,获取到segments在偏移量为u位置的分段Segment,
        //并且分段Segment中对应table数组不为空
        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;
                //3.直接拿到数据返回,但是可能这个数据被其他线程修改,弱一致性.
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

4.put

put方法直接使用了Segment的方法,在put之前尝试加锁,加锁成功则进入操作,加锁失败则循环cas尝试获取锁,当达到一定次数后,锁住等待其他线程释放锁.

图示:

在这里插入图片描述
这边主要涉及到三个方法如下:

1.ConcurrentHashMap.put
    public V put(K key, V value) {
        Segment<K,V> s;
        if (value == null)
            throw new NullPointerException();
        //1.计算哈希值、找到对应的Segment.
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null)
            s = ensureSegment(j);
        //2.直接调用Segment的put方法
        return s.put(key, hash, value, false);
    }
2.Segment.put
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            //1.先尝试对segment加锁,如果直接加锁成功,那么node=null;如果加锁失败,则会调用scanAndLockForPut方法去获取锁
            HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                //2.table是volatile,因为锁定之后不会被修改,所以赋值给普通变量能减少volatile的消耗
                HashEntry<K,V>[] tab = table;
                //3.计算哈希值,查找对应的HashEntry数组
                int index = (tab.length - 1) & hash;
                HashEntry<K,V> first = entryAt(tab, index);
                //4.循环当前数组的所有HashEntry节点
                for (HashEntry<K,V> e = first;;) {
                    //5.当前节点不为空,进入匹配
                    if (e != null) {
                        K k;
                        //6.匹配成功,赋值和退出循环
                        if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    //6.当前节点为空,这边的方式应该也是属于前插法
                    else {
                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        //7.超过大小扩容
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                //8.解锁
                unlock();
            }
            return oldValue;
        }
3.Segment.scanAndLockForPut
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
            HashEntry<K,V> first = entryForHash(this, hash);
            HashEntry<K,V> e = first;
            HashEntry<K,V> node = null;
            int retries = -1;
            //1.如果尝试加锁失败,那么就对segment[hash]对应的链表进行遍历找到需要put的这个entry所在的链表中的位置,
            //这里之所以进行一次遍历找到坑位,主要是为了通过遍历过程将遍历过的entry全部放到CPU高速缓存中,
            //这样在获取到锁了之后,再次进行定位的时候速度会十分快,这是在线程无法获取到锁前并等待的过程中的一种预热方式。
            while (!tryLock()) {
                HashEntry<K,V> f;
                //2.获取锁失败,初始时retries=-1必然开始先进入第一个if
                if (retries < 0) {
                    //3.第一次进来第一个节点为空,给他赋值,判断node为空,是因为后面判断其他线程改变链表之后,
                    //避免node节点在之前的遍历中赋值,导致重复赋值
                    if (e == null) {
                        if (node == null) {
                            node = new HashEntry<K,V>(hash, key, value, null);
                        }
                        retries = 0;
                    }
                    //4.遍历过程发现链表中找到了我们需要的key的坑位
                    else if (key.equals(e.key)){
                        retries = 0;
                    }
                    //往下遍历
                    else{
                        e = e.next;
                    }
                }
                //5.尝试获取锁次数超过设置的最大值,直接进入阻塞等待,这就是所谓的有限制的自旋获取锁,
                //之所以这样是因为如果持有锁的线程要过很久才释放锁,这期间如果一直无限制的自旋其实是对系统性能有消耗的,
                //这样无限制的自旋是不利的,所以加入最大自旋次数,超过这个次数则进入阻塞状态等待对方释放锁并获取锁。
                //unlock释放锁,必定是在外面的方法结束之后去释放.
                else if (++retries > MAX_SCAN_RETRIES) {
                    lock();
                    break;
                }
                //6.遍历过程中,有可能其它线程改变了遍历的链表,这时就需要重新进行遍历了。
                else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) {
                    e = first = f;
                    retries = -1;
                }
            }
            return node;
        }

4.remove

remove操作同样用到了有限次自旋CAS锁加锁,对整个的remove操作结束之后会释放锁

    public V remove(Object key) {
        int hash = hash(key);
        Segment<K,V> s = segmentForHash(hash);
        //查找segment,如果segment不为空,则调用它的remove方法
        return s == null ? null : s.remove(key, hash, null);
    }
final V remove(Object key, int hash, Object value) {
    //1.没有拿到锁,就进入循环cas锁获取、达到一定次数挂起
     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;
         //2.通过哈希值获取的HashEntry不为空
         while (e != null) {
             K k;
             HashEntry<K,V> next = e.next;
             //3.获取到对应的key
             if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                 V v = e.value;
                 if (value == null || value == v || value.equals(v)) {
                     //4.当前值为空,直接设置next值在当前位置
                     if (pred == null)
                         setEntryAt(tab, index, next);
                     //5.否则直接把指针指向e节点的下一个节点(1.e = next;2.next=e.next),也就是略过该节点(删除)
                     else
                         pred.setNext(next);
                     ++modCount;
                     --count;
                     oldValue = v;
                 }
                 break;
             }
             pred = e;
             e = next;
         }
     } finally {
         //解锁
         unlock();
     }
     return oldValue;
 }

5.replace

replace和remove道理差不多类似

    public V replace(K key, V value) {
        //1.找到对应hash的segment
        int hash = hash(key);
        if (value == null)
            throw new NullPointerException();
        Segment<K,V> s = segmentForHash(hash);
        //2.进行replace操作或者直接返回null
        return s == null ? null : s.replace(key, hash, value);
    }
final V replace(K key, int hash, V value) {
            //1.获取锁和有限次cas获取锁
            if (!tryLock())
                scanAndLock(key, hash);
            V oldValue = null;
            try {
                HashEntry<K,V> e;
                //2.遍历对应的HashEntry节点
                for (e = entryForHash(this, hash); e != null; e = e.next) {
                    K k;
                    //3.找到匹配的进行换值操作
                    if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                        oldValue = e.value;
                        e.value = value;
                        ++modCount;
                        break;
                    }
                }
            } finally {
                //4.解锁
                unlock();
            }
            return oldValue;
        }

6.clear

clear会循环所有segment进行清除操作,并且清除时锁住每个segment,防止其他线程进入

    public void clear() {
        final Segment<K,V>[] segments = this.segments;
        //1.循环所有的segment进行移除操作
        for (int j = 0; j < segments.length; ++j) {
            Segment<K,V> s = segmentAt(segments, j);
            if (s != null)
                s.clear();
        }
    }
final void clear() {
            //1.锁定当前segment,这样应该也是为了get、put操作不能直接进来
            lock();
            try {
                //2.清除所有元素
                HashEntry<K,V>[] tab = table;
                for (int i = 0; i < tab.length ; i++)
                    setEntryAt(tab, i, null);
                ++modCount;
                count = 0;
            } finally {
                //3.解锁
                unlock();
            }
        }
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

了-凡

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值