- get的时候如果发现桶的头节点hash值小于0,则代表是forwardingNode或者是TreeBIn,需要去新数组中查找或者走红黑树的逻辑(TreeBin里面有一个红黑色结构,包含一个TreeNode的root),这个treebin在树化的时候创造,赋值hash为-2;
- put的时候如果发现痛的头节点hash值为-1,则表示正在扩容,则会去帮忙扩容。
- 在remove和put的时候,如果是单线程,会对basecount进行操作,如果是多线程,则对cell[]数组中的累加单元进行操作(cas)。因此map统计的元素数量就是对这个数组进行累加,不一定是实时的。
- Java 8 数组(Node) +( 链表 Node | 红黑树 TreeNode ) 以下数组简称(table),链表简称(bin)
- 初始化,使用 cas 来保证并发安全,懒惰初始化 table 树化,当 table.length < 64 时,先尝试扩容,超过 64时,并且 bin.length > 8 时,会将链表树化,树化过程 会用 synchronized 锁住链表头
- put,如果该 bin尚未创建,只需要使用 cas 创建 bin;如果已经有了,锁住链表头进行后续 put 操作,元素 添加至 bin 的尾部
- get,无锁操作仅需要保证可见性,扩容过程中 get 操作拿到的是 ForwardingNode 它会让 get 操作在新 table进行搜索 扩容,扩容时以 bin 为单位进行,需要对 bin 进行 synchronized,但这时妙的是其它竞争线程也不是无事可做,它们会帮助把其它 bin 进行扩容,扩容时平均只有 1/6 的节点会把复制到新 table 中
- size,元素个数保存在 baseCount 中,并发时的个数变动保存在 CounterCell[] 当中。最后统计数量时累加 即可
- JDK 7 ConcurrentHashMap 它维护了一个 segment 数组,每个 segment 对应一把锁 优点:如果多个线程访问不同的segment,实际是没有冲突的,这与 jdk8 中是类似的 缺点:Segments数组默认大小为16,这个容量初始化指定后就不能改变了,并且不是懒惰初始化 。segment 继承了可重入(ReentrantLock)发生在 put 中,因为此时已经获得了锁,因此 rehash 时不需要考虑线程安全
jdk8中ConcurrentHashMap的特点
最新推荐文章于 2024-05-21 23:02:43 发布