悟空老师,您说的是对的,ConcurrentHashMap之前我没有看源码,想当然的认为和HashMap一样会将红黑树的头节点移动到链表的第一个位置,从而改变链表的结构。
如下是HashMap的源码
moveRootToFront会将平衡后红黑树的头节点移动到链表的第一个位置。其他Node在链表的位置不变。
而ConcurrentHashMap并没有这么做,而是用一个TreeBin的数据结构记录了红黑树的头节点,如下
当链表中添加了一个Node从而满足条件转换为红黑树后,会调用treeifyBin()方法
treeyIfyBin方法还是会和HashMap一样维护一个双向链表,这个双向链表的顺序和单向链表的顺序是一模一样的
并且会将双向链表头节点构建为TreeBin类型的,通过TreeBin中维护的root属性构建红黑树或者通过红黑树查找相应的节点。
回到正题,为什么ConcurrentHashMap的读没有锁,在写时候的读也不会出错呢?
首先Node中的value属性和next属性都被volatile修饰,volatile保证可见性,所以当线程A通过链表读取一个节点时,这时线程B在链表末尾添加了这个节点,因为volatile保证了可见性,所以线程A能立马感知到新添加的这个节点。
当线程A通过链表读取一个节点时,线程B添加这个节点到链表末尾,即使线程B添加这个节点达到转换为红黑树的条件,但是线程B也是首先将这个节点添加到单向链表之后,才将单向链表转换为双向链表,进而转为红黑树。 此时单向链表的结构并没有改变,所以线程A因为volatile的原因也能感知到新添加的这个节点,当线程A再次调用读取方法时,才会通过红黑树的方式查找。
所以ConcurrentHashMap的get并不需要添加任何锁,也能保证写同时读的安全性。