ConcurrentHashMap在JDK1.7以及1.8的不同
1.结构
1.7
ConcurrentHashMap在JDK1.7的实现是使用 Segment数组+ HashEntry数组 组成。Segment继承了ReentrantLock,自带锁的功能。一个segment里面包含一个HashEntry数组,想要对HashEntry操作,则必须先获得Segment的锁。
1.8
ConcurrentHashMap JDK1.8,抛弃了Segment的设计,使用了 Node
+ CAS
+Synchronized
的方式
2.初始化
1.7
JDK1.7的ConcurrentHashMap初始化的时候已经将Segment数组初始化,并且添加第一个Segment。c初始大小为16,代码如下
/**
* Creates a new, empty map with the specified initial
* capacity, load factor and concurrency level.
*
* @param initialCapacity the initial capacity. The implementation
* performs internal sizing to accommodate this many elements.
* @param loadFactor the load factor threshold, used to control resizing.
* Resizing may be performed when the average number of elements per
* bin exceeds this threshold.
* @param concurrencyLevel the estimated number of concurrently
* updating threads. The implementation performs internal sizing
* to try to accommodate this many threads.
* @throws IllegalArgumentException if the initial capacity is
* negative or the load factor or concurrencyLevel are
* nonpositive.
*
* jdk1.7
*/
@SuppressWarnings("unchecked")
public ConcurrentHashMap7(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;
// Segment 数组大小
int ssize = 1;
// 一般应该会左移4次,sshift = 4 ssize = 16
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
// 一般是28
this.segmentShift = 32 - sshift;
// 掩码是ssize-1 最大值是65535 2的16次方-1
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
// create segments and segments[0]
// 第一个Segment
Segment s0 =
new Segment(loadFactor, (int)(cap * loadFactor),
(HashEntry[])new HashEntry[cap]);
// Segment数组
Segment[] ss = (Segment[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
1.8
JDK1.8的ConcurrentHashMap只有在第一次put的时候才开始初始化 Node
数组(inittable()),构造方法的时候只记录大小值,代码如下
/*
* jdk 1.8 concurrent
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
}
/*
* 初始化table
*/
private final Node[] initTable() {
Node[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// sizeCtl map大小值
if ((sc = sizeCtl) < 0)
// lost initialization race; just spin
// 线程让步 不释放锁
Thread.yield();
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
3. put
1.7
JDK1.7添加元素,先通过key的Hash值,根据掩码散列定位对应的Segment,如果没有找到初始化的segment,就通过CAS生成,再通过segment的put方法插入元素。
segment插入过程,会先通过tryLock获取锁,然后将对应元素插入到HashEntry数组中,然后再释放锁。如果一开始tryLock没有获取锁,会调用scanAndLockForPut()方法获取锁(while(tryLock()),或有重试次数限制,超过的话会将线程挂起,等待获取锁的线程处理结束。
代码如下:
/**
* concurrentHashMap的put方法
*/
public V put(K key, V value) {
Segment s;
if (value == null)
throw new NullPointerException();
// 定位Segment
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
// 通过CAS初始化Segment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
/*
* segment 的put
*/
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 获取锁 成功则继续,失败走scanAndLockForPut 重复tryLock
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry first = entryAt(tab, index);
for (HashEntry e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
else
node = new HashEntry(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
1.8
JDK1.8的添加元素方法,也是先通过key的hash值散列定位对应的Node。判断流程如下:
- 是否存在Node数组,没有initTable();
- 是否找得到对应的Node节点,没有的话CAS插入元素
- Node的hash值是否处于移动状态(-1)
- 节点加锁(synchronized),判断Node是什么结构(链表or Tree),走相关的插入方法
- 判断bincount是否不为0 ,不为0以为这进行了数据操作,先判断是否需要转换成树,再判断oldVal是否有值,如果有,说明只是更新,不增加节点,直接返回旧值oldVal
- 统计总数添加
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node[] tab = table;;) {
Node f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// Node节点不存在,CAS 插入元素
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
// 处于移动状态
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
// Node节点存在,且不处于移动状态
else {
V oldVal = null;
// 锁住节点
synchronized (f) {
// 如果 Node可以定位到
if (tabAt(tab, i) == f) {
// 链表结构
if (fh >= 0) {
binCount = 1;
for (Node e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
// 树状结构
else if (f instanceof TreeBin) {
Node p;
binCount = 2;
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 节点可以被定位到
if (binCount != 0) {
// 是否需要转换成树结构
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 统计
addCount(1L, binCount);
return null;
}
4. size
1.7
JDK1.7的concurrentHashMap使用一个变量modCount进行记录是否进行了变化,如果进行了put,remove,clean的操作,则给modcount加1,统计前后比较是否modcount发生变化。
先采用不加锁的方式,连续计算元素的个数,最多计算3次:
1、如果前后两次计算结果相同,则说明计算出来的元素个数是准确的;
2、如果前后两次计算结果都不同,则给每个Segment
进行加锁,再计算一次元素的个数;
public int size() {
// Try a few times to get accurate count. On failure due to
// continuous async changes in table, resort to locking.
final Segment[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts
long last = 0L; // previous sum
int retries = -1; // first iteration isn't retry
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 seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount;
int c = seg.count;
if (c < 0 || (size += c) < 0)
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;
}
1.8
JDK1.8通过volatile 修饰的long变量baseCount来统计元素个数,部分元素的变化个数放在counterCell数组中。通过累加baseCount
和CounterCell
数组中的数量,即可得到元素的总个数
添加删除节点的时候通过addCount()方法来修改值。逻辑如下:
- 初始化时
counterCells
为空,在并发量很高时,如果存在两个线程同时执行CAS
修改baseCount
值,则失败的线程会继续执行方法体中的逻辑(put/remove方v法是一个无限for循环),使用CounterCell
记录元素个数的变化; - 如果
CounterCell
数组counterCells
为空,调用fullAddCount()
方法进行初始化,并插入对应的记录数,通过CAS
设置cellsBusy字段,只有设置成功的线程才能初始化CounterCell
数组 - 如果通过
CAS
设置cellsBusy字段失败的话,则继续尝试通过CAS
修改baseCount
字段,如果修改baseCount
字段成功的话,就退出循环,否则继续循环插入CounterCell
对象
代码如下:
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
/*
* x > 0 添加数据,x < 0删除数据 ,check添加的时候是bincount,删除的时候是-1
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// counterCells为空或者CAS失败,回到原有方法重新来
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
// 判断是否需要初始化CounterCell[] 数组
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
/*
* 初始化 CounterCell 数组
*/
private final void fullAddCount(long x, boolean wasUncontended) {
int h;
if ((h = ThreadLocalRandom.getProbe()) == 0) {
// force initialization 强制初始化
ThreadLocalRandom.localInit();
h = ThreadLocalRandom.getProbe();
wasUncontended = true;
}
// True if last slot nonempty 数组没有位置,则为true
boolean collide = false;
for (;;) {
CounterCell[] as; CounterCell a; int n; long v;
if ((as = counterCells) != null && (n = as.length) > 0) {
if ((a = as[(n - 1) & h]) == null) {
if (cellsBusy == 0) { // Try to attach new Cell
CounterCell r = new CounterCell(x); // Optimistic create
// 修改cellBusy成功的线程初始化 数组
if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean created = false;
try { // Recheck under lock
CounterCell[] rs; int m, j;
if ((rs = counterCells) != null &&
(m = rs.length) > 0 &&
rs[j = (m - 1) & h] == null) {
rs[j] = r;
created = true;
}
} finally {
cellsBusy = 0;
}
if (created)
break;
continue; // Slot is now non-empty
}
}
collide = false;
}
else if (!wasUncontended) // CAS already known to fail
wasUncontended = true; // Continue after rehash
else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))
break;
else if (counterCells != as || n >= NCPU)
collide = false; // At max size or stale
else if (!collide)
collide = true;
else if (cellsBusy == 0 &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
try {
if (counterCells == as) {// Expand table unless stale
CounterCell[] rs = new CounterCell[n << 1];
for (int i = 0; i < n; ++i)
rs[i] = as[i];
counterCells = rs;
}
} finally {
cellsBusy = 0;
}
collide = false;
continue; // Retry with expanded table
}
h = ThreadLocalRandom.advanceProbe(h);
}
else if (cellsBusy == 0 && counterCells == as &&
U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) {
boolean init = false;
try { // Initialize table
if (counterCells == as) {
CounterCell[] rs = new CounterCell[2];
rs[h & 1] = new CounterCell(x);
counterCells = rs;
init = true;
}
} finally {
cellsBusy = 0;
}
if (init)
break;
}
else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x))
break; // Fall back on using base
}
}
5.参考内容
《并发编程的艺术》-方腾飞 魏鹏 程晓明
本文由博客一文多发平台 OpenWrite 发布!