:::warning 和HashTable的区别? ::: ConcurrentHashMap采用了分段锁的设计,只有在同一个分段内才存在竞态关系,不同的分段锁之间没有锁竞争。相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现。

    :::warning 分段锁的机制? ::: ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。ConcurrentHashMap中的HashEntry相对于HashMap中的Entry有一定的差异性:HashEntry中的value以及next都被volatile修饰,这样在多线程读写过程中能够保持它们的可见性。
    如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个Segment内的访问会扩散到不同的Segment中,CPU cache命中率会下降,从而引起程序性能下降。

    :::warning 如何获取全局视图(count、contain)? ::: 首先不加锁循环执行以下操作:循环所有的Segment(通过Unsafe的getObjectVolatile()以保证原子读语义),获得对应的值以及所有Segment的modcount之和。如果连续两次所有Segment的modcount和相等,则过程中没有发生其他线程修改ConcurrentHashMap的情况,返回获得的值。

    :::warning jdk8的实现区别? ::: 它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现,利用CAS算法。它沿用了与它同时期的HashMap版本的思想,底层依然由“数组”+链表+红黑树的方式思想。
    在ConcurrentHashMap中,随处可以看到U, 大量使用了U.compareAndSwapXXX的方法,这个方法是利用一个CAS算法实现无锁化的修改值的操作,他可以大大降低锁代理的性能消耗。这个算法的基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值是否相等,如果相等,则接受你指定的修改的值,否则拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。

    1. //获得在i位置上的Node节点
    2. static final <K,V> Node<K,V> tabAt(Node<K,V>[] tab, int i) {
    3. return (Node<K,V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE);
    4. }
    5. //利用CAS算法设置i位置上的Node节点。之所以能实现并发是因为他指定了原来这个节点的值是多少
    6. //在CAS算法中,会比较内存中的值与你指定的这个值是否相等,如果相等才接受你的修改,否则拒绝你的修改
    7. //因此当前线程中的值并不是最新的值,这种修改可能会覆盖掉其他线程的修改结果 有点类似于SVN
    8. static final <K,V> boolean casTabAt(Node<K,V>[] tab, int i,
    9. Node<K,V> c, Node<K,V> v) {
    10. return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v);
    11. }
    12. //利用volatile方法设置节点位置的值
    13. static final <K,V> void setTabAt(Node<K,V>[] tab, int i, Node<K,V> v) {
    14. U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v);
    15. }

    :::warning jdk8的全局视图? :::

    1. final long sumCount() {
    2. CounterCell[] as = counterCells; CounterCell a;
    3. long sum = baseCount;
    4. if (as != null) {
    5. for (int i = 0; i < as.length; ++i) {
    6. if ((a = as[i]) != null)
    7. sum += a.value;
    8. }
    9. }
    10. return sum;
    11. }

    没有竞争的情况下,要累加的数通过cas累加到base上;如果有竞争的话,会将要累加的数累加到Cells数组中的某个cell元素里面。chm会为每个线程维护一个counterCell[]数组元素,一旦修改basecount并发冲突,就会将count加到cell数组中,所以在统计总数时,需要baseCount+所有cellCount。并且countCell添加了@Contented注解,来解决内存伪共享问题。
    image.png