Base 1.7
    1.ConcurrentHashMap存储结构是由 Segment 数组、HashEntry 组成,和 HashMap 一样,仍然是数组加链表。
    2.HashEntry和HashMap非常类似,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。
    3.原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。
    put 方法

    1. public V put(K key, V value) {
    2. Segment<K,V> s;
    3. if (value == null)
    4. throw new NullPointerException();
    5. int hash = hash(key);
    6. int j = (hash >>> segmentShift) & segmentMask;
    7. if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
    8. (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
    9. s = ensureSegment(j);
    10. return s.put(key, hash, value, false);
    11. }

    首先是通过 key 定位到 Segment,之后在对应的 Segment 中进行具体的 put。

    1. final V put(K key, int hash, V value, boolean onlyIfAbsent) {
    2. HashEntry<K,V> node = tryLock() ? null :
    3. scanAndLockForPut(key, hash, value);
    4. V oldValue;
    5. try {
    6. HashEntry<K,V>[] tab = table;
    7. int index = (tab.length - 1) & hash;
    8. HashEntry<K,V> first = entryAt(tab, index);
    9. for (HashEntry<K,V> e = first;;) {
    10. if (e != null) {
    11. K k;
    12. if ((k = e.key) == key ||
    13. (e.hash == hash && key.equals(k))) {
    14. oldValue = e.value;
    15. if (!onlyIfAbsent) {
    16. e.value = value;
    17. ++modCount;
    18. }
    19. break;
    20. }
    21. e = e.next;
    22. }
    23. else {
    24. if (node != null)
    25. node.setNext(first);
    26. else
    27. node = new HashEntry<K,V>(hash, key, value, first);
    28. int c = count + 1;
    29. if (c > threshold && tab.length < MAXIMUM_CAPACITY)
    30. rehash(node);
    31. else
    32. setEntryAt(tab, index, node);
    33. ++modCount;
    34. count = c;
    35. oldValue = null;
    36. break;
    37. }
    38. }
    39. } finally {
    40. unlock();
    41. }
    42. return oldValue;
    43. }

    虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性(并发的原子性指的是即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行),所以 put 操作时仍然需要加锁处理。
    首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。

    1. 尝试自旋获取锁。
    2. 如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁(阻塞锁指的是多个线程同时调用同一个方法的时候,所有线程都被排队处理了。让线程进入阻塞状态进行等待,当获得相应的信号(唤醒,时间) 时,才可以进入线程的准备就绪状态,准备就绪状态的所有线程,通过竞争,进入运行状态)获取,保证能获取成功。

    put流程:

    1. 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
    2. 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
    3. 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
    4. 最后会解除当前 Segment 的锁。

    get流程:
    get 逻辑比较简单:
    只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。
    由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。
    ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。

    Base 1.8
    1.7相对来说已经解决了并发的问题,但在查询遍历链表效率低。
    1.8使用的数据结构采用数组+链表+红黑树,抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。且其中的val next也使用了volatile 关键词修饰。
    put流程:

    • 根据 key 计算出 hashcode 。
    • 判断是否需要进行初始化。
    • f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
    • 如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
    • 如果都不满足,则利用 synchronized 锁写入数据。
    • 如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

    get流程:

    • 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
    • 如果是红黑树那就按照树的方式获取值。
    • 就不满足那就按照链表的方式遍历获取值。

    1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。