【图解】面试题:ConcurrentHashMap是如何保证线程安全的

注意:JDK1.7与JDK1.8中的ConcurrentHashMap主要延续HashMap的设计与思想,是在其基础上进行的相应优化

1.JDK1.7中的底层实现原理
(1)JDK1.7ConcurrentHashMap的底层结构,延续HashMap的底层设计,采用数组+链表

(2)不同的是:ConcurrentHashMap中的数组被分为大数组和小数组,大数组是Segment,小数组是HashEntry
Segment本身是基于ReentrantLock可重入锁来实现加锁和释放锁,这样就能保证多线程同时访问ConcurrentHashMap的时候,同一时间只能有一个线程操作对应的节点,以此来保证线程安全

总结:这里基于Segment加锁和释放锁,因此称之为分段锁或分片锁

在这里插入图片描述

2.JDK1.8中的底层实现原理
(1)JDK1.8的ConcurrentHashMap底层采用的是数组+链表+红黑树,这里对JDK1.7进行了优化,在链表长度大于8并且数组长度大于64时链表就会转化为红黑树

(2)JDK1.8中保留了Segment的定义,这仅仅是为了保证序列化时的兼容性,不再有任何结构上的用途

(3)JDK1.8中主要使用volatile+CAS或者是synchronized的方法来实现的,以此来保证线程安全
(i)源码中,添加元素时,首先会判断容器是否为空,为空,就会使用volatile加CAS来初始化
(ii)容器不为空,就会根据存储的元素计算该位置是否为空,如果计算结果位置的结果为空,就会使用CAS来设计该节点,不为空,就会使用synchronized来加锁实现,然后遍历桶中的数据并且替换或者新增节点到桶中,最后判断是否需要转换红黑树

总结:JDK1.8中ConcurrentHashMap是通过对头节点加锁来保证线程安全,降低锁粒度,发生hash冲突和加锁的频率也更低了

在这里插入图片描述
附上JDK1.8源码

 /**
     * Maps the specified key to the specified value in this table.
     * Neither the key nor the value can be null.
     *
     * <p>The value can be retrieved by calling the {@code get} method
     * with a key that is equal to the original key.
     *
     * @param key key with which the specified value is to be associated
     * @param value value to be associated with the specified key
     * @return the previous value associated with {@code key}, or
     *         {@code null} if there was no mapping for {@code key}
     * @throws NullPointerException if the specified key or value is null
     */
    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;
            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;
                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;
    }
posted @ 2022-04-28 09:53  debug的勇士  阅读(110)  评论(17编辑  收藏  举报