JUC全文
Unsafe
- java后门类,提供硬件级别的
原子操作
,进行底层操作
部分Api
arrayBaseOffset
获取数组的基础偏移量,在内存中的位置arrayIndexScale
获取数组元素中的偏移间隔,获取数组中对应的元素,offset + idx * scale
getObjectVolatile
获取对象的属性值或数组中的元素putOrderVolatile
设置对象的属性值或数组中某个角标的元素,不保证线程间可见性,不保证线程安全,更高效putObjectVolatile
设置对象的属性值或者数组中的某个角标的元素,保证可见性
使用
- 获取
Unsafe
static Unsafe unsafe;
static {
Field field = null;
try {
field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
- 使用
Integer[] arr = new Integer[]{1, 2, 3, 4};
//基础偏移
int baseOffset = unsafe.arrayBaseOffset(Integer[].class);
//数据间隔
int indexScale = unsafe.arrayIndexScale(Integer[].class);
//获取元素
Object o = unsafe.getObjectVolatile(arr, 2 * indexScale + baseOffset);
System.out.println(o);
//设置元素
unsafe.putOrderedObject(arr, 2 * indexScale + baseOffset, 6);
System.out.println(Arrays.toString(arr));
//如果 arr 的 2 * indexScale + baseOffset 的位置的值为 6 那么就设置为101
boolean res = unsafe.compareAndSwapObject(arr, 2 * indexScale + baseOffset, 3, 101);
System.out.println(res);
System.out.println(Arrays.toString(arr));
ConcurrentHashMap-jdk1.7
- 内部结构有一个
Segment<K,V>[] segments
,每一个segment
中有HashEntry<K,V>[] table
,用来真正存放数据(被封装成HashEntry对象)
,每个Entry
都可以存储一个链表
数组 ) 数组 ) 链表
- 分段锁思想,可以为不同
segment
进行上锁动作,可以让多线程同时读写不同的segment
提高并发,保证线程安全
初始化
- 最大容量为
1 << 16
个segment
*1 << 30
个HashEntry
- 空构造,默认大小为32,16个
segment
* 2个HashEntry
public ConcurrentHashMap() {
this(DEFAULT_INITIAL_CAPACITY, //默认为16
DEFAULT_LOAD_FACTOR, //默认为0.75,
DEFAULT_CONCURRENCY_LEVEL); //默认为16,segments数组的大小
}
ssize
为segement数组
大小,必须为2的整数幂次
- 每一个
segment
的HashEntry的数组大小
默认为2,且也需要为2的整数幂次
;并将s0
设置给ss
的0
位置,SBASE = UNSAFE.arrayBaseOffset(sc);
,此时是没有线程安全问题的
此时
segments
只有一个元素,其中的HashEntry
也只有一个初始容量为2,相当于模版对象
- 确定位置时,保留的位置,保留高位的
sshift
,下标范围就在0 ~ 2 ^ sshift - 1
this.segmentShift = 32 - sshift;
segment
下标范围
this.segmentMask = ssize - 1;
- c为每个
HashEntry
的大小,int
存在向下取底,需要保证最后的个数大于initialCapacity
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
Segment
- 继承
ReentrantLock
HashEntry
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
获取位置
- 需要确定放在哪个
segment
和哪个entry
- 高位确定哪个
segment
- 低位确定哪个
entry
- 不允许有空值
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
//计算出hash值
int hash = hash(key);
//高位,先确定放哪个segment,无符号移动,前面补0
int j = (hash >>> segmentShift) & segmentMask;
//取出Segment
if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
//初始化Segment
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
hash(key)
会报空指针异常(j << SSHIFT) + SBASE)
等价于j * scale + SBASE
Class sc = Segment[].class; //数组类
ss = UNSAFE.arrayIndexScale(sc);//偏移间隔
//numberOfLeadingZeros前面有多少个0
//确定最高位在第几位
//j << SSHIFT 可以保证正好移动了(j - 1)个Scale,指向segments[j]的起点地址
//如果能保证ss是2的整数次幂这里就能解释得通...不然想不太明白
SSHIFT = 31 - Integer.numberOfLeadingZeros(ss);
ensureSegment
尝试取了三次,保证多线程下其他线程创建了一个,通过不用锁的方式保证线程安全
put
- 调用
segment
的put
的方法,此时能够保证segment
的安全。tryLock
加锁成功则为null
;加锁失败调用scanAndLockForPut
- 计算角标,调用
entryAt
取对应角标元素 - 遍历当前
HashEntry
的链表节点,onlyIfAbsent
如果只能没有时才能设置值,就不替换旧值 - 待加入元素没有重复的添加逻辑
setEntryAt
放置元素,此时是在上锁的条件下使用的,可以用putOrderedObject
static final <K,V> void setEntryAt(HashEntry<K,V>[] tab, int i,
HashEntry<K,V> e) {
UNSAFE.putOrderedObject(tab, ((long)i << TSHIFT) + TBASE, e);
}
scanAndLockForPut
加锁失败逻辑,自旋式抢锁,retries
尝试的次数- 开始尝试抢锁,在自旋的过程中处理node,如果存在
key
相等,就不需要新建,也不会管返回的node
是否为null
,后续比对时,直接回替换相同的key
的value
;不存在则新建 - 后续自旋操作,大于64次调用
lock()
直接阻塞,等待锁释放
cpu核数决定自旋次数,最多64次
static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
扩容
put
中,元素个数大于当前segment
的threshold
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(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]; //新数据
int sizeMask = newCapacity - 1; //新数据的下标范围
- 迁移工作
for (int i = 0; i < oldCapacity ; i++) { //遍历该segment中每一个位置
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
int idx = e.hash & sizeMask;
if (next == null) //如果只有单个节点直接移动
newTable[idx] = e;
else { //加快迁移效率,目的是找到最下面连续的,且可以放在一个下标的链表
HashEntry<K,V> lastRun = e; //起始点
int lastIdx = idx; //起始坐标
//节点迁移有两个位置可以放,原位置/原位置+旧长度
for (HashEntry<K,V> last = next;
last != null;
last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
//下一个跟上一个坐标不相等,那么lastRun指针向后移动
//直到找到末尾,连续的可以放在一个角标的链表头
//例子 原下标 0 原长度 2 原元素 2 4 8 6 10
//新长度 4 新下标 0 处的元素 4 8 新下标 2 处的元素 2 6 10
// 6 10 就可以一起迁移
lastIdx = k;
lastRun = last;
}
}
//直接迁移这个头元素
newTable[lastIdx] = lastRun;
// 处理其他节点,直接复制一份,头插法,原下标后面的,会变到前面
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);
}
}
}
}
- 放新的元素
int nodeIndex = node.hash & sizeMask; // 新下标
node.setNext(newTable[nodeIndex]); //头插法
newTable[nodeIndex] = node;
table = newTable;
获取集合长度
- 通过
操作次数是否变动
保证长度正确性,至少要循环两次/最多五次
,才能获得长度,要先为last
复制
- 要进行第四次时,会对所有
segement
进行上锁,而这期间也会存在操作修改,因此第五次时,
public int size() {
final Segment<K,V>[] segments = this.segments;
int size;
boolean overflow; // true if size overflows 32 bits
long sum; // sum of modCounts操作次数总和上一次的操作次数
int retries = -1; // 重试次数
try {
for (;;) { //死循环
if (retries++ == RETRIES_BEFORE_LOCK) { //等于2,下面进行三次 -1 0 1
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) { //遍历所有segement
Segment<K,V> seg = segmentAt(segments, j);
if (seg != null) {
sum += seg.modCount; //累加集合的操作次数
int c = seg.count; //size的累加,累加segment中的实际个数
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;
}
get
public V get(Object key) {
Segment<K,V> s;
HashEntry<K,V>[] tab;
int h = hash(key);
//获取当前segment的位置
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) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
remove
public V remove(Object key) {
int hash = hash(key); //key的哈希
Segment<K,V> s = segmentForHash(hash); //该hash对应的segment
return s == null ? null : s.remove(key, hash, null);
}
remove
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; //tab中的角标
HashEntry<K,V> e = entryAt(tab, index); //获取角标的链表
HashEntry<K,V> pred = null;
while (e != null) { //遍历该双向链表
K k;
HashEntry<K,V> next = e.next;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) { //找到匹配的节点
V v = e.value;
if (value == null || value == v || value.equals(v)) {
if (pred == null)
setEntryAt(tab, index, next);
else
pred.setNext(next);
++modCount;
--count;
oldValue = v;
}
break;
}
pred = e;
e = next;
}
} finally {
unlock();
}
return oldValue;
}
小结
- 数据结构为
segment[]+tab[]+单向链表
- 确定位置
- 高位确定
segment
- 低位确定
tab
- 容量
segment
和tab
容量也要为2的整数幂次- 默认容量
segment
为16,tab
为2,最少为32个- 最大容量
segment
为1<<16,tab
为1<<30
- 存放元素
- 上锁,上锁成功,正常遍历,头插法
- 上锁失败,自旋抢锁,默认自旋
64/1
次就进入阻塞,过程中搜索节点,key是否相等/新建节点
- 获取
size
- 通过比较操作次数,来确定是否发生变化
- 具体大小,累加每个
segment
的个数- 最少循环两次,最多循环五次(第四次循环开始前,全部上锁,并且更新操作次数,第五次在计算操作次数,相等后,break)
- 扩容
- 大小扩大两倍
- 迁移优化,找到一个链表最末端连续可以放在一个角标的部分,直接迁移,而不是单个迁移