ConcurrentHashMap-Jdk7-源码解读
- 以下代码截自Jdk-7u80
- 关于Unsafe的知识了解不多,本文不多讨论,欢迎大佬教学
- 文章中会将Segment叫做段,HashEntry[]数组叫做桶数组
1. ConcurrentHashMap数据结构

ConcurrentHashMap本质为一个Segment数组,Segment继承了ReentrantLock类,即Segment类自带一把锁,在操作数据时对当前segment加锁以保证并发安全,这就是ConcurrentHashMap支持高并发的分段锁原理(在Jdk1.7中)。
Segment是一个哈希数组(数据结构与1.7中的HashMap类似),组内元素为链表形式的HashEntry对象。
HashEntry是最终的键值对结构。
数据结构从小到大为:HashEntry < Segment < ConcurrentHashMap
2. HashEntry
HashEntry是ConcurrentHashMap数据结构中的最小存储单元,对应<K,V>键值对,通过next指向下一个节点,数据结构相对简单:
static final class HashEntry<K,V> {
final int hash;// key的hash值
final K key;
volatile V value;
volatile HashEntry<K,V> next;// 指向下一个节点
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// 设置Next节点
final void setNext(HashEntry<K,V> n) {
UNSAFE.putOrderedObject(this, nextOffset, n);
}
// Unsafe 相关的方法
// Unsafe mechanics
static final sun.misc.Unsafe UNSAFE;
static final long nextOffset;// next成员的偏移地址
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class k = HashEntry.class;
// 得到next成员在对象中的偏移量,用来进行链接操作
// 利用UnSafe类可以通过直接操作内存来提高速度
nextOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("next"));
} catch (Exception e) {
throw new Error(e);
}
}
}
3. Segment
Segment继承了ReentrantLock,可以通过加锁来保证线程安全。Segment的底层也是由数组实现,数组中为HashEntry构成的链表结构(整体结构与HashMap相似)。
3.1 成员变量/类变量
// 最大重试次数,在 scanAndLockForPut()、 scanAndLock() 方法中用到
// availableProcessors()返回java虚拟机的可用处理器数
static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
// 键值对数组
transient volatile HashEntry<K,V>[] table;
// 当前segment的键值对数量
transient int count;
// 操作数,该数据只增不减
transient int modCount;
// 扩容阈值 table.length * loadFactor = threshold
transient int threshold;
// 负载因子
final float loadFactor;
在ConcurrentHashMap中有一些方法不是直接加锁执行,而是进行多次遍历,通过modCount判断是否发生改变,循环次数通过MAX_SCAN_RETRIES来控制。
3.2 主要方法
put()
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 如果获取到了锁,则向下执行,否则执行 scanAndLockForPut 方法
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry<K,V>[] tab = table;
// 计算key在当前 Segment 中的数组下标
int index = (tab.length - 1) & hash;
// 获取第一个元素
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {// 判断key是否一致(== 或者 hash相等&&equals相等)
oldValue = e.value;
// 根据传入参数判断是否需要替换,true表示相同key时不替换,false表示替换
if (!onlyIfAbsent) {
e.value = value;
++modCount;// 操作数++
}
break;// 找到了目标key
}
e = e.next;
}
else {// 数组为空或者遍历到链表的最后也没找到同key
if (node != null)// node不为空,说明 scanAndLockForPut 方法存在返回值
node.setNext(first);// 头插法
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;// 元素数量+1
// 判断是否需要扩容(超过扩容阈值且长度小于最大值时可扩容)
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
// 将元素node放入数组中
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();// 解锁
}
return oldValue;// oldValue为空则表示没有找到相同的key
}
scanAndLockForPut()/scanAndLock()
// 方法作用:创建新结点或找到key相同的结点并加锁,为添加做准备,用于put方法
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 找到key对应的桶中的第一个元素
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
// 记录尝试次数
int retries = -1; // negative while locating node
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
// 桶为空或已到最后
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))// 找到了equals一致的key
retries = 0;
else
e = e.next;
}
// 如果扫描次数大于阈值,则强制获取锁
else if (++retries > MAX_SCAN_RETRIES) {
lock();// 超过最大尝试次数就强制加锁,若获取不了锁,则会阻塞(对本Segment对象加锁)
break;
}
// (retries & 1) == 0,当retries最低位不为1时成立
// TODO ??
// 有一种情况下会死循环:假设key每次都能找到equals一样的,retries=0,下次循环++retries后&1为true,若一直在修改first(put或者替换),则需要一直重置
else if ((retries & 1) == 0 &&
// 重新计算桶的第一个元素,如果!=first,表示当前桶被修改过,则需要重新循环
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
// 返回hash对应节点的位置
return node;
}
// 这个方法跟上边的方法基本类似,只是少了创建新节点的步骤,用在remove、replace方法
private void scanAndLock(Object key, int hash) {
// similar to but simpler than scanAndLockForPut
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f;
if (retries < 0) {
if (e == null || key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f;
retries = -1;
}
}
}
rehash()
// segment.table扩容
@SuppressWarnings("unchecked")
private void rehash(HashEntry<K,V> node) {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
// 扩容一倍
int newCapacity = oldCapacity << 1;
// 计算新阈值
threshold = (int)(newCapacity * loadFactor);
// 初始化新数组
HashEntry<K,V>[] newTable =
(HashEntry<K,V>[]) new HashEntry[newCapacity];
// length - 1,后续用来计算 hash&(length-1) 确定所在桶
int sizeMask = newCapacity - 1;
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];// e表示当前桶位的第一个元素
if (e != null) {
HashEntry<K,V> next = e.next;// 获得链表下一个元素
int idx = e.hash & sizeMask;
if (next == null) // Single node on list
newTable[idx] = e;// 当前链表只有一个元素,直接放入目标桶
else { // Reuse consecutive sequence at same slot
// 链表不只一个元素,需要遍历
HashEntry<K,V> lastRun = e;// lastRun,从lastRun到链表尾部的元素都是一个桶,算是个优化吧
int lastIdx = idx;// lastIdx,lastRun的桶
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;// 计算桶位
if (k != lastIdx) {// ①
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;// ② 这部分数据不用走之后的循环
// Clone remaining nodes
// 循环clone剩下的元素
// 为啥要克隆?不能直接用么?
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;// 重定位
HashEntry<K,V> n = newTable[k];// 获取当前第一个元素
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);// 新建元素,next指向n,放入桶中
}
}
}
}
// 插入node元素
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}
代码中①处比较难以理解,用以下案例来做个说明:
当前桶位数:16,扩容后桶位数:32
计算桶位的算法:ex.index = x & (length - 1) = x % (length - 1)

追踪一下lastRun、lastIdx变化的过程:
| lastRun | lastIdx | last | k | 说明 |
|---|---|---|---|---|
| e2 | e | e18 | 18 | 此时k!=lastIdx,改变lastRun、lastIdx的值 |
| e18 | 18 | e34 | 2 | 此时k!=lastIdx,改变lastRun、lastIdx的值 |
| e34 | 2 | e66 | 2 | 此时k=lastIdx,开始下一次循环 |
| e34 | 2 | e98 | 2 | 此时k=lastIdx |
newTable[lastIdx] = lastRun
将最后的下标为lastIdx的节点直接放到新数组的lastIdx节点下,然后遍历lastRun之前的节点,依次clone到新数组。
当一条链表的最后几个节点重定位的下标相同时,直接将这几个节点放到新的数组中。
remove()
// value为null时,只匹配key,否则两者都匹配
final V remove(Object key, int hash, Object value) {
// 尝试加锁,加锁失败就循环加锁,且找到目标值
if (!tryLock())
scanAndLock(key, hash);
V oldValue = null;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;// 计算桶位
HashEntry<K,V> e = entryAt(tab, index);// 找到目标桶的第一个元素
HashEntry<K,V> pred = null;// pred 指向前一个元素
while (e != null) {
K k;
HashEntry<K,V> next = e.next;
// key相等或者 hash相等且key.equals相等
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
V v = e.value;
// 如果value为空(只匹配key) || value相等 || equals相等,找到目标元素
if (value == null || value == v || value.equals(v)) {
if (pred == null)
// 桶内第一个就是,将next放到桶第一位
setEntryAt(tab, index, next);
else
// 否则前一个指向next
pred.setNext(next);
++modCount;// 操作数+1
--count;// 原子数-1
oldValue = v;// 返回旧值
}
break;
}
// 遍历下一个元素
pred = e;
e = next;
}
} finally {
unlock();// 解锁
}
return oldValue;
}
replace()
// key+value判断
final boolean replace(K key, int hash, V oldValue, V newValue) {
// 加锁
if (!tryLock())
scanAndLock(key, hash);
boolean replaced = false;// 用来标记是否替换成功
try {
HashEntry<K,V> e;
for (e = entryForHash(this, hash); e != null; e = e.next) {// 获取桶位,然后遍历
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {// (key==) 或 (e.hash相等且key.equals相等)
if (oldValue.equals(e.value)) {// 判断值.equals是否相等
e.value = newValue;
++modCount;
replaced = true;
}
break;
}
}
} finally {
unlock();
}
return replaced;
}
// 只用key判断
final V replace(K key, int hash, V value) {};
clear()
// 清空段位
final void clear() {
lock();
try {
HashEntry<K,V>[] tab = table;
for (int i = 0; i < tab.length ; i++)
setEntryAt(tab, i, null);// 直接循环将桶位置null
++modCount;
count = 0;
} finally {
unlock();
}
}
4. ConcurrentHashMap
4.1 常量/变量
// 默认初始化容量,这个容量并不是最终的段数或者桶数
static final int DEFAULT_INITIAL_CAPACITY = 16;
// 默认负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 默认隔离级别,即段数
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 段最小容量,即段内HashEntry数组长度
static final int MIN_SEGMENT_TABLE_CAPACITY = 2;
// 最大段数
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
// 加锁最大重试次数,size()方法有用到
static final int RETRIES_BEFORE_LOCK = 2;
// 用来确定哈希值中参与段定位的高位的位数
final int segmentShift;
// 在段内索引的移位值,用来确定段的索引
final int segmentMask;
// 段数组
final Segment<K,V>[] segments;
4.2 构造函数
public ConcurrentHashMap(int initialCapacity, float loadFactor) {
this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
}
public ConcurrentHashMap(int initialCapacity,
float loadFactor, int concurrencyLevel) {
// 校验参数
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
// 设置隔离级别,即段数
if (concurrencyLevel > MAX_SEGMENTS)
concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;// 表示段数组长度
// 找到 ssize >= concurrencyLevel 的最小的2次幂
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
// hash值中段所占的位数??,作用是什么?用来计算段的位置,不太清楚原理
this.segmentShift = 32 - sshift;
// 用来计算目标段下标 hash&(size-1)
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;// 不能超过最大容量
// c表示每个段的hash表长度
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)// 如果为浮点数,则向上+1
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;// 每个段的hash表长度,2的次幂,最小为2
// create segments and segments[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
// 创建段数组
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
// ConcurrentHashMap是懒加载的方式,除了第一个段之外,其他的都是在用到的时候才初始化
// 写入segments[0],put时会以segments[0]为原型创建新的segment,所以直接创建了第一个段
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
// 赋值给段数组
this.segments = ss;
}
public ConcurrentHashMap(Map<? extends K, ? extends V> m) {
// +1是为了向上取整,有可能造成一定空间浪费
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY),
DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);
putAll(m);
}
4.3 主要方法
entryAt()
// 找到tab中第i个元素
static final <K,V> HashEntry<K,V> entryAt(HashEntry<K,V>[] tab, int i) {
return (tab == null) ? null :
(HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)i << TSHIFT) + TBASE);
}
setEntryAt()
// 将 e 放到 tab 的第 i 个元素处
static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
HashEntry<K,V> e) {
UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}
segmentAt()
// 找到组第j个段
static final <K,V> Segment<K,V> segmentAt(Segment<K,V>[] ss, int j) {
long u = (j << SSHIFT) + SBASE;
return ss == null ? null :
(Segment<K,V>) UNSAFE.getObjectVolatile(ss, u);
}
put()
// key和value都不能是空的,若匹配到相同key,则替换并返回旧值
public V put(K key, V value) {
Segment<K,V> s;
// 这个地方并没有规定key不能为null,但是在hash(key)会爆出来空指针,为啥不直接指定呢?
if (value == null)
throw new NullPointerException();
int hash = hash(key);// 扰动后的hash值,没看懂
// 通过散列值的高n位来确定段下标
int j = (hash >>> segmentShift) & segmentMask;
// 根据物理地址来取得目标段
// SSHIFT :Segment数组每个元素的偏移量,SBASE :Segment数组第一个元素的偏移量
// 由上面两个偏移量可以算出目标下标元素的物理地址
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
// 确认段信息,若段不存在,则创建段
s = ensureSegment(j);
// 调用 Segment.put
return s.put(key, hash, value, false);
}
// 只是最后调用 Segment.put 传参不同,key相同时不再替换
public V putIfAbsent(K key, V value){}
// 循环调用put(注:不会动原有的entry,每次都是新建,但是key和value用的是原有的)
public void putAll(Map<? extends K, ? extends V> m) {}
ensureSegment()
// 返回k对应的段,若不存在就创建(CAS算法)
private Segment<K,V> ensureSegment(int k) {
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
// 以segments[0]为原型
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];// 创建segment.table
// 二次检查是否有其它线程创建了这个Segment
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) { // recheck
// 创建segment
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
// 这里通过自旋的CAS方式对segments数组中偏移量为u位置设置值为s,这是一种不加锁的方式,
// 万一有多个线程同时执行这一步,那么只会有一个成功,而其它线程在看到第一个执行成功的线程结果后
// 会获取到最新的数据从而发现需要更新的坑位已经不为空了,那么就跳出while循环并返回最新的seg
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u))
== null) {
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
return seg;// 返回目标段
}
isEmpty()
public boolean isEmpty() {
long sum = 0L;
final Segment<K,V>[] segments = this.segments;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
if (seg.count != 0)// count不为空,表示存在元素,直接返回false
return false;
sum += seg.modCount;// 统计操作数
}
}
// 二次检查操作数是否发生变化
if (sum != 0L) { // recheck unless no modifications
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
if (seg.count != 0)
return false;
sum -= seg.modCount;
}
}
if (sum != 0L)// 因为操作数是衡增的,两次统计中,若操作数发生变化,则表示源列表不为空
return false;
}
return true;
}
size()
// 返回键值对的个数,如果超过了Integer.MAX_VALUE,则返回Integer.MAX_VALUE
public int size() {
// 先不加锁尝试,超过RETRIES_BEFORE_LOCK次时,加锁统计
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits,true表示超过32位
long sum; // sum of modCounts,操作数之和
long last = 0L; // previous sum,上一次的操作数
int retries = -1; // first iteration isn't retry,重试次数,第一次不算所以是-1
try {
for (;;) {
// 判断重试次数,此处是给所有的段加锁
if (retries++ == RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation,强行加锁
}
sum = 0L;
size = 0;
overflow = false;
for (int j = 0; j < segments.length; ++j) {
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;// 操作数
int c = seg.count;// 键值对数
if (c < 0 || (size += c) < 0)// int超过最大数之后就变成负的了,表示超过32位
overflow = true;
}
}
if (sum == last)// 此次循环和上一次循环一致,则跳出循环
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {// 段解锁
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return overflow ? Integer.MAX_VALUE : size;
}
get()
// key存在则返回对应的值,否则返回null
public V get(Object key) {
// 段
Segment<K,V> s; // manually integrate access methods to reduce overhead
HashEntry<K,V>[] tab;// 桶数组
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
if ((s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
for (HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {//获取h对应这个分段中偏移量为xxx下的HashEntry的链表头结点,然后对链表进行 遍历
// 这里第一次初始化通过getObjectVolatile获取HashEntry时,获取到的是主存中最新的数据,但是在后续遍历过程中,有可能数据被其它线程修改
// 从而导致其实这里最终返回的可能是过时的数据,所以这里就是ConcurrentHashMap所谓的弱一致性的体现,containsKey方法也一样
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
contains()
/** 如果找到一个或多个value,则返回true,否则返回value,需要遍历数组,会比较慢 */
public boolean containsValue(Object value) {
// Same idea as size()
// 参照size方法的方式
if (value == null)
throw new NullPointerException();
final Segment<K,V>[] segments = this.segments;
boolean found = false;// 结果
long last = 0;
int retries = -1;// 重试次数
try {
outer: for (;;) {
if (retries++ == RETRIES_BEFORE_LOCK) {
// 超过RETRIES_BEFORE_LOCK次时,加锁统计
for (int j = 0; j < segments.length; ++j)
ensureSegment(j).lock(); // force creation
}
long hashSum = 0L;
int sum = 0;
for (int j = 0; j < segments.length; ++j) {
HashEntry<K,V>[] tab;
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null && (tab = seg.table) != null) {
for (int i = 0 ; i < tab.length; i++) {
HashEntry<K,V> e;
for (e = entryAt(tab, i); e != null; e = e.next) {
V v = e.value;
if (v != null && value.equals(v)) {
found = true;
break outer;
}
}
}
sum += seg.modCount;
}
}
if (retries > 0 && sum == last)
break;
last = sum;
}
} finally {
if (retries > RETRIES_BEFORE_LOCK) {
for (int j = 0; j < segments.length; ++j)
segmentAt(segments, j).unlock();
}
}
return found;
}
// 就是containsValue,遗留方法
public boolean contains(Object value) {}
// map中是否包含key,跟get差不多,只是返回boolean
public boolean containsKey(Object key) {}
其他方法
以下方法基本都是定位段,然后调用segment对应的方法,不再赘述:
- public V remove(Object key) {}
- public boolean remove(Object key, Object value) {}
- public boolean replace(K key, V oldValue, V newValue) {}
- public V replace(K key, V value) {}
- public void clear() {}
5. 总结
- ConcurrentHashMap由Segment数组结构和HashEntry数组组成。Segment是一种可重入锁,是一种数组和链表的结构,一个Segment中包含一个HashEntry数组,每个HashEntry又是一个链表结构。正是通过Segment分段锁,ConcurrentHashMap实现了高效率的并发;
- 段数为大于等于concurrencyLevel的最小的2次幂,段的hash表的最小长度为2;
- ConcurrentHashMap的段数量在初始化后不会改变,扩容至会发生在段内,2倍扩容;
- ConcurrentHashMap存在弱一致性,get()、containsKey()(这两个方法没有加锁)可能读到旧的结果;
- ConcurrentHashMap创建segment时,采用CAS算法;
本文详细解析了JDK7中ConcurrentHashMap的实现原理,包括其数据结构、HashEntry、Segment以及核心方法如put、remove和size。ConcurrentHashMap通过Segment数组和HashEntry链表实现高并发访问,每个Segment继承自ReentrantLock,通过分段锁确保并发安全性。在put操作中,使用scanAndLockForPut方法尝试加锁和寻找目标位置,rehash方法处理扩容。size()方法通过多次尝试避免加锁,实现弱一致性。整个结构保证了高效并发的同时,提供了良好的扩展性。
1866

被折叠的 条评论
为什么被折叠?



