ConcurrentHashMap源码分析

jdk1.7

数据结构

1.7的时候底层是由segments数组+HashEntry数组+链表组成的,在1.8的时候又不同。

    // segments数组
    final Segment<K,V>[] segments;

    static final class Segment<K,V> extends ReentrantLock implements Serializable {
        // table数组 和 HashMap的table差不多,是拿来真正存放数据的
        transient volatile HashEntry<K,V>[] table;
        // table中的元素个数
        transient int count;
        transient int modCount;
        // 阈值
        transient int threshold;
        // 负载因子
        final float loadFactor;
        .......
        .......
    }

HashEntry和HashMap中的Entry差不多,不同的地方是,用了volatile去修饰Valuenext

    static final class HashEntry<K,V> {
        final int hash;
        final K key;
        // 使用 volatile修饰
        volatile V value;
        volatile HashEntry<K,V> next;
        .......
        ......
    }

volatile保证不同线程对这个变量进行实时操作的可见性(实现可见性)和禁止指令重排序(实现有序性)。

put操作

    public V put(K key, V value) {
        Segment<K,V> s;
        // 不允许value值为null,如果为null就抛空指针异常
        if (value == null)
            throw new NullPointerException();
        // 获取hash
        int hash = hash(key);
        int j = (hash >>> segmentShift) & segmentMask;
        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck
             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
            s = ensureSegment(j);
        // 调用下方的put方法
        return s.put(key, hash, value, false);
    }

上面👆的是先找到对应的segment,再进行下面👇的put操作

        final V put(K key, int hash, V value, boolean onlyIfAbsent) {
            // 尝试获取锁,如果获取失败则scanAndLockForPut(...)自旋获取锁
            HashEntry<K,V> node = tryLock() ? null :
                scanAndLockForPut(key, hash, value);
            V oldValue;
            try {
                // 获取当前segment的table数组
                HashEntry<K,V>[] tab = table;
                // 计算对应的数组下标index
                int index = (tab.length - 1) & hash;
                // 获取对应下标的HashEntry
                HashEntry<K,V> first = entryAt(tab, index);
                // 遍历该HashEntry
                for (HashEntry<K,V> e = first;;) {
                    // 如果不为空
                    if (e != null) {
                        K k;
                        // 判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。            
                        if ((k = e.key) == key ||
                            (e.hash == hash && key.equals(k))) {
                            oldValue = e.value;
                            if (!onlyIfAbsent) {
                                e.value = value;
                                ++modCount;
                            }
                            break;
                        }
                        e = e.next;
                    }
                    else {
                        // 为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。

                        if (node != null)
                            node.setNext(first);
                        else
                            node = new HashEntry<K,V>(hash, key, value, first);
                        int c = count + 1;
                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)
                            rehash(node);
                        else
                            setEntryAt(tab, index, node);
                        ++modCount;
                        count = c;
                        oldValue = null;
                        break;
                    }
                }
            } finally {
                unlock();
            }
            return oldValue;
        }

get操作

    public V get(Object key) {
        Segment<K,V> s; // manually integrate access methods to reduce overhead
        HashEntry<K,V>[] tab;
        // 计算key对应的hash
        int h = hash(key);
        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
        // 通过hash获取到具体的segment
        if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
            (tab = s.table) != null) {
            // 通过hash获取到具体HashEntry,并遍历该HashEntry
            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;
                // 传入的key和当前遍历的key相等,返回对应的value
                if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                    return e.value;
            }
        }
        return null;
    }

1. 将key通过hash后定位到具体的segment,然后再通过hash定位到具体的HashEntry
2. 遍历该HashEntry,查找和传入key相等的HashEntry.key,返回该HashEntry的value。
因为 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。

ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁

jdk1.8

数据结构

抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
和HashMap类似,将HashMap改成了Node,引入了红黑树
数据结构为:数组+链表/红黑树,当链表数量>=8的时候,转换为红黑树,当数量<=6时,红黑树转换为链表

put操作

    public V put(K key, V value) {
        return putVal(key, value, false);
    }

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        // 如果key或value为null的时候,抛异常
        if (key == null || value == null) throw new NullPointerException();
        // 根据key.hashCode获取进行散列后的hash
        int hash = spread(key.hashCode());
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            // 判断数组是否为null,进行初始化
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
                // 当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
                if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break;                   // no lock when adding to empty bin
            }
            // 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            // 如果都不满足,则利用synchronized锁写入
            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) {
                    // 如果数量大于 TREEIFY_THRESHOLD 则转换为红黑树
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (oldVal != null)
                        return oldVal;
                    break;
                }
            }
        }
        addCount(1L, binCount);
        return null;
    }

文字步骤:

  1. 根据 key 计算出 hashcode 。

  2. 判断是否需要进行初始化。

  3. 为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。

  4. 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。

  5. 如果都不满足,则利用 synchronized 锁写入数据。

  6. 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

get操作

    public V get(Object key) {
        Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
        // 将key.hashCode()进行散列化
        int h = spread(key.hashCode());
        // 定位该hash对应table数组中的Node
        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;
    }
posted @ 2020-08-22 22:52  jealous-boy  阅读(109)  评论(0编辑  收藏  举报