本书是基于JDK7创作的,所以下面说的性质都是JDK7特性的总结
- 为什么要使用ConcurrentHashMap?
- JDK7中ConcurrentHashMap的结构
- JDK7中ConcurrentHashMap的初始化过程
- key-value进入后,如何判断属于那个segment呢?
- get操作
- put操作
- size操作
为什么要使用ConcurrentHashMap
- HashMap不是线程安全的,在并发环境下,put操作会发生死循环
- Hashtable是线程安全的,但是每个操作都是synchronized修饰,太慢
- ConcurrentHashMap也用锁,但是不锁全部的Map,使用 分段锁。
```java
//jdk7 死循环代码
public static void main(String[] args) throws InterruptedException {
final Map<String,String> map = new HashMap<>(2);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
new Thread(new Runnable() {
@Override
public void run() {
map.put(UUID.randomUUID().toString(), "");
// System.out.println("hello");
}
}, "gx" + i).start();
}
}
}, "gx");
thread.start();
thread.join();
}
<a name="ImVSn"></a>
# JDK7中ConcurrentHashMap结构
![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1642593712504-c93fd9fb-8772-457d-8bca-505bd592da88.png#clientId=u610d18a7-77e4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=514&id=uf3d59647&margin=%5Bobject%20Object%5D&name=image.png&originHeight=463&originWidth=914&originalType=binary&ratio=1&rotation=0&showTitle=false&size=198934&status=done&style=none&taskId=u8fdde053-23ed-4dbe-a92d-5a0d5069c68&title=&width=1015.555582458591)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/1609516/1642593759528-641c9513-79d4-413b-a9a1-865cd951bd26.png#clientId=u610d18a7-77e4-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=830&id=u3a00271d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=747&originWidth=893&originalType=binary&ratio=1&rotation=0&showTitle=false&size=274716&status=done&style=none&taskId=u0633d8b7-5bda-4702-b8fb-68727fa4fb4&title=&width=992.2222485071354)
- 一个ConcurrentHashMap里包含一个Segment数组
- 每个Segment基础ReentrantLock
- 每个Segment的结构又和一个HashMap结构类似,数组链表
- HashEntry数组,每个数组的元素是一个链表。
<a name="QURFK"></a>
# JD7中初始化过程
<a name="l5rlr"></a>
## 重要参数
- initialCapacity:初始化容量,默认16
- loadFactor:加载因子,默认 0.75
- concurrencyLevel:指定Segement数组长度,默认16
用上面三个参数来初始化下面参数
- segment数组:
- segmentShift:段偏移量
- segmentMask:段掩码
- segment中的HashEntry数组
<a name="Uq8Ng"></a>
## 初始化segments数组和掩码和偏移量
```java
//三个参数都要大于0
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;
//指定segment数组的长度,必须2^N
int ssize = 1;
while (ssize < concurrencyLevel) {
//偏移次数
++sshift;
ssize <<= 1;
}
//偏移量 据左边
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
- concurrenyLevel用来计算segments数组的长度
- 最后以ssize为准,是2^N
- sshift计算的是1左移次数,也就是ssize的二进制值,然后用32-sshift得到segmentShift
- segmentMask等于ssize-1
初始化每个segment
```java if (initialCapacity > MAXIMUM_CAPACITY)
//初始容量 / segment数组大小 , 每个segment中应该有多少个元素 int c = initialCapacity / ssize; //不能整除要+1, 可多不可少 if (c * ssize < initialCapacity)initialCapacity = MAXIMUM_CAPACITY;
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// 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];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
- cap:是segment中HashEntry数组的长度,最小值为2
- cap*loadFacotr:是每个segment的阈值。
<a name="LWkBY"></a>
# 定位Segment
一个key-value对进入后,如何判断其放在哪个segment中。<br />是通过Wang/Jenkins hash的变种算法对元素的hashCode进行再散列
```java
private int hash(Object k) {
int h = hashSeed;
if ((0 != h) && (k instanceof String)) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// Spread bits to regularize both segment and index locations,
// using variant of single-word Wang/Jenkins hash.
h += (h << 15) ^ 0xffffcd7d;
h ^= (h >>> 10);
h += (h << 3);
h ^= (h >>> 6);
h += (h << 2) + (h << 14);
return h ^ (h >>> 16);
}
通过上面的方法计算了散列值后,通过下面一段代码找到具体的index
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
- hash后的结果int,无符号右移后&掩码的作用是让长度落在数组范围
get操作
get操作流程简单经过一次再散列,定位到segment,再调用segment的get获取值
get操作整体若读取的值是空,才会加锁重读。
原因:get方法将要使用的共享变量定义成了 volatile。
在segment中的散列是通过hashCode直接&
put操作
put操作方法必须加锁