赞
踩
@[TOC](`jdk 1.8 ConcurrentHashMap和1.7 ConcurrentHashMap区别 原理解析)
#在上一篇文章中,我们详细链接了HashMap在1.7和1.8和实现区别,但是我们都知道HashMap在多线程下是不安全的,所以jdk也为我们提供了并发安全的容器进行使用,那就是我们这篇文章的主角:ConcurrentHashMap。
我们首先看一下内部结构
从源码我们了解到,ConcurrentHashMap 是由 Segment 数组结构和 HashEntry 数组结构组成。Segment 是一种可重入锁(ReentrantLock),在 ConcurrentHashMap 里扮演锁的角色;HashEntry 则用于存储键值对数据。一个 ConcurrentHashMap 里包含一Segment 数组。Segment 的结构和 HashMap 类似,是一种数组和链表结构。一Segment 里包含一个 HashEntry 数组,每个 HashEntry 是一个链表结构的元素,每个 Segment 守护着一个 HashEntry 数组里的元素,当对 HashEntry 数组的数据进行修改时,必须首先获得与它对应的 Segment 锁
构造方法和初始化:ConcurrentHashMap 初始化方法是通过 initialCapacity、loadFactor 和concurrencyLevel(参数 concurrencyLevel 是用户估计的并发级别,就是说你觉得最
多有多少线程共同修改这个 map,根据这个来确定 Segment 数组的大小concurrencyLevel 默认是 DEFAULT_CONCURRENCY_LEVEL = 16;)等几个参数来初始化 segment 数组、段偏移量 segmentShift、段掩码 segmentMask 和每个 segment里的 HashEntry 数组来实现的
并发级别可以理解为程序运行时能够同时更新 ConccurentHashMap 且不产
生锁竞争的最大线程数,实际上就是 ConcurrentHashMap 中的分段锁个数,即
Segment[]的数组长度。ConcurrentHashMap 默认的并发度为 16,但用户也可以
在构造函数中设置并发度。当用户设置并发度时,ConcurrentHashMap 会使用大
于等于该值的最小 2 幂指数作为实际并发度(假如用户设置并发度为 17,实际
并发度则为 32)。如果并发度设置的过小,会带来严重的锁竞争问题;如果并发度设置的过大,原本位于同一个 Segment 内的访问会扩散到不同的 Segment 中,CPU cache 命中率会下降,从而引起程序性能下降。segments 数组的长度 ssize 是通过 concurrencyLevel 计算得出的。为了能通过按位与的散列算法来定位 segments 数组的索引,必须保证 segments 数组的长度是 2 的 N 次方(power-of-two size),所以必须计算出一个大于或等于concurrencyLevel 的最小的 2 的 N 次方值来作为 segments 数组的长度
既然 ConcurrentHashMap 使用分段锁 Segment 来保护不同段的数据,那么在插入和获取元素的时候,必须先通过散列算法定位到 Segment。ConcurrentHashMap 会首先使用 Wang/Jenkins hash 的变种算法对元素的hashCode 进行一次再散列。ConcurrentHashMap 完全允许多个读操作并发进行,读操作并不需要加锁。
ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的以及volatile 关键
字
get 操作先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment(使用了散列值的高位部分),再通过散列算法定位到 table(使用了散列值的全部)。整个 get 过程,没有加锁,而是通过 volatile 保证 get 总是可以拿到最新值
ConcurrentHashMap 初始化的时候会初始化第一个槽 segment[0],对于其他
槽,在插入第一个值的时候再进行初始化。ensureSegment 方法考虑了并发情况,多个线程同时进入初始化同一个槽segment[k],但只要有一个成功就可以了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。