读操作一般不加锁(TreeBin的读写锁除外),读写操作可并行;因为C13Map的写操作都要获取bin头部的syncronized互斥锁,能保证最多只有一个线程在做更新,<b>单线程写、多线程</b>读的并发安全性的问题
如果读取的bin是一个链表,<br>头节点是个普通Node
如果正在发生链表向红黑树的treeify()(链表转树结构)工作,<br>因为treeify本身并不破坏旧的链表bin的结构,只是在全部treeify完成后将头节点一次性<br>替换为新创建的TreeBin,可以放心读取。
如果正在发生<b>resize</b>且当前bin正在被transfer,因为transfer本身并不破坏旧的链表bin的结构,<br>只是在全部transfer完成后将头节点一次性替换为ForwardingNode,可以放心读取。<br>
<b>扩容期间hash桶查询数据会发生什么?</b>
扩容前,扩容中可以访问原数组
正在迁移的hash 桶
迁移形成的链是复制的,而非移动,复制不影响原数组的遍历,不会阻塞get操作
扩容完成的
头节点的hash 为负数表示整个数组在扩容
头节点标记为ForwardNode
转发到新数组进行查询
如果其它线程正在操作链表,在当前线程遍历链表的任意一个时间点,都有可能同时在发生add/replace/remove操作。<br><b>ConcurrentHashMap 弱一致性</b>
如果是add操作,因为链表的节点新增从JDK8以后都采用了<b>尾插法,会多遍历或者少遍历一个tailNode节点</b>
如果是remove操作,存在遍历到某个Node时,正好有其它线程将其remove,导致其孤立<br>于整个链表之外;<br>但因为其<b>next引用未变</b>,整个链表并没有断开,还是可以照常遍历链表知道tailNode.
如果是replace 操作,链表的结构未变;只是某个Node的value 发生了变化,没有安全问题。
不能保证happen before
结论:链表<b>线性数据</b>结构,单线程写且插入操作尾插法,并发读取是安全的;<br>不会存在误读、链表断开导致的漏读、读到环状链表等问题。