Java集合框架阅读笔记(三)ConcurrentHashMap

预备知识

  • AQS(AbstractQueuedSynchronizer):提供了一个框架用来构造同步一些工具类比如ReentrantLock、 CopyOnWriteArrayList、 CountDownLatch、Semaphore等。通过维护一个volatile修饰的变量state表示锁的获取情况和一个FIFO队列用来存储线程。

API:

Provides a framework for implementing blocking locks and related
synchronizers (semaphores, events, etc) that rely on
first-in-first-out (FIFO) wait queues. This class is designed to
be a useful basis for most kinds of synchronizers that rely on a
single atomic {@code int} value to represent state.

ConcurrentHashMap

介绍

  • ConcurrentHashMap是一个线程安全的HashMap,继承体系如下
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
    implements ConcurrentMap<K,V>, Serializable

JDK8和JDK7的区别

存储结构

  • JDK8存储结构采用Node类型数组加链表并通过拉链法解决hash冲突而JDK7基于AQS实现了一个Segment类继承自ReentrantLock,每个Segment对象持有一个HashEntry数组。Node和HashEntry都是实现了Map.Entry接口,只不过名字不同。

并发更新操作的实现

  • JDK8通过synchronized+CAS机制进行并发更新,锁对象是数组下标对应的元素持有的Monitor。JDK7继承了AQS里的ReentrantLock进行加锁实现的并发更新。

计算size

  • JDK8通过维护一个由volatile修饰的baseCount变量进行计数,以及一个CounterCell类进行记录变化的次数来确定size。JDK7采用延迟计算,在计算过程中会对每个Segment计算至少两次,如果出现数据不一致现象就进行全部加锁最后求得size。

部分源码分析

public V put(K key, V value)

直接调用putVal(K key, V value, boolean onlyIfAbsent)

public V put(K key, V value) {
    return putVal(key, value, false);
}
  • JDK8
final V putVal(K key, V value, boolean onlyIfAbsent) {
    // 预处理
    if (key == null || value == null) throw new NullPointerException();
    // 为了分布均匀对key的hashcode再hash
    int hash = spread(key.hashCode());
    // 根据binCount通过计算链表长度,判断链表是否修改过,如果链表的数量超过设定的阈值,插入完成之后需要变成红黑树
    int binCount = 0;
    // 初始化tab,死循环直到插入成功
    for (Node<K,V>[] tab = table;;) {
        // 指向目标节点
        Node<K,V> f; 
        int n, i, fh;
        // 延迟加载:如果没有初始化进行初始化
        if (tab == null || (n = tab.length) == 0)
            tab = initTable();
        // 如果数组下标对应的元素为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
        }
        // 如果当前节点的hash值为MOVED常量,则表示这个ConcurrentHashMap正在进行扩容,则当前线程帮助扩容helpTransfer(tab,f)。
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        // 当前数组下标有元素,并且可以正常插入,则根据节点元素类型是红黑树节点还是链表节点然后执行插入
        else {
            V oldVal = null;
            // 加锁,Monitor对象是当前数组下标元素持有的Monitor
            synchronized (f) {
                // tabAt(tab,i)会通过Unsafe类求出数组下标元素,再次判断确保是否是上面所确定的f
                if (tabAt(tab, i) == f) {
                    // 代表是链表元素,执行插入链表操作
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek;
                            // 如果已经存在此key,则更新即可
                            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;
                        }
                    }
                }
            }
            // 根据binCount判断链表元素个数
            if (binCount != 0) {
                // 超过阈值,则将链表变成红黑树
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (oldVal != null)
                    return oldVal;
                break;
            }
        }
    }
    // 将当前维护的baseCount + 1
    addCount(1L, binCount);
    return null;
}
  • JDK7
public V put(K key, V value) {
    Segment<K, V> s;
    if (value == null) throw new NullPointerException();
    int hash = hash(key);
    // UNSAFE通过hash找到Segment
    int j = (hash >>> segmentShift) & segmentMask;
    if ((s = (Segment<K, V>) UNSAFE.getObject          // nonvolatile; recheck
            (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment
        s = ensureSegment(j);
    return s.put(key, hash, value, false);
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    // 加锁,如果加锁成功则执行后续操作,没有竞争到锁就需要自旋直到获取到锁并执行插入操作。
    HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
    V oldValue;
    try {
        // 找到引用的数组
        HashEntry<K,V>[] tab = table;
        // 求模计算数组下标
        int index = (tab.length - 1) & hash;
        // 找到数组第一个元素
        HashEntry<K,V> first = entryAt(tab, index);
        // 死循环,直到插入成功
        for (HashEntry<K,V> e = first;;) {
            // 数组对应下标已经存在数值。
            if (e != null) {
                K k;
                // 已经存在此key,进行更新
                if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
                    oldValue = e.value;
                    if (!onlyIfAbsent) {
                        e.value = value;
                        // 记录每一个Segment中的HashEntry数组中元素的数量
                        ++modCount;
                    }
                    break;
                }
                e = e.next;
            // 如果当前数组下标已经存在元素
            } else {
                // 当前线程可能自旋过(自旋过程可能会返回一个已经初始化的node),所以需要判断node是否为null
                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;
}

public V get(Object key)

  • JDK8
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;
}
  • JDK7
public V get(Object key) {
    Segment<K, V> s; // manually integrate access methods to reduce overhead
    HashEntry<K, V>[] tab;
    int h = hash(key);
    long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
    // 通过UNSAFE获取Segment,通过Segment获取HashEntry数组引用,进而找到指定的key
    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;
            if ((k = e.key) == key || (e.hash == h && key.equals(k)))
                return e.value;
        }
    }
    return null;
}

public int size()

  • JDK8

通过由volatile修饰的baseCount变量以及CounterCell对象记录变化的次数求出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;
    // 获取baseCount
    long sum = baseCount;
    if (as != null) {
        // 循环遍历每一个CountCell中的value
        for (int i = 0; i < as.length; ++i) {
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}
  • JDK7

通过计算Segment数组中每一个Segment中的modcount(在put时会++modCount)求出sum。但是不止求一次sum,会至少求两次(由RETRIES_BEFORE_LOCK决定的),如果两次求出的sum不一致,下次求就会将Segment数组全部加锁,重新求一次。

public int size() {
    // Try a few times to get accurate count. On failure due to
    // continuous async changes in table, resort to locking.
    final Segment<K, V>[] segments = this.segments;
    int size;
    boolean overflow; // true if size overflows 32 bits
    long sum;         // sum of modCounts
    long last = 0L;   // previous sum
    int retries = -1; // first iteration isn't retry
    try {
        for (; ; ) {
            // 超出两次,将Segment全部加锁
            if (retries++ == RETRIES_BEFORE_LOCK) {
                for (int j = 0; j < segments.length; ++j)
                    ensureSegment(j).lock(); // force creation
            }
            sum = 0L;
            size = 0;
            overflow = false;
            for (int j = 0; j < segments.length; ++j) {
                Segment<K, V> seg = segmentAt(segments, j);
                if (seg != null) {
                    // 获取Segment对象的modCount
                    sum += seg.modCount;
                    int c = seg.count;
                    if (c < 0 || (size += c) < 0)
                        overflow = true;
                }
            }
            // 和上一次求的结果比较
            if (sum == last)
                break;
            last = sum;
        }
    } finally {
        if (retries > RETRIES_BEFORE_LOCK) {
            for (int j = 0; j < segments.length; ++j)
                segmentAt(segments, j).unlock();
        }
    }
    return overflow ? Integer.MAX_VALUE : size;
}

参考

  • https://www.jianshu.com/p/e694f1e868ec
  • http://penghb.com/2017/10/27/java/concurrentHashMap/
  • http://www.importnew.com/26049.html
  • https://swenfang.github.io/2018/06/03/Java%208%20ConcurrentHashMap%20%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/

猜你喜欢

转载自www.cnblogs.com/jinshuai86/p/9463614.html
今日推荐