没有合适的资源?快使用搜索试试~ 我知道了~
Java 多线程与并发(13-26)-JUC集合- ConcurrentHashMap详解.pdf
0 下载量 106 浏览量
2023-07-26
18:25:21
上传
评论
收藏 553KB PDF 举报
温馨提示
试读
24页
Java 多线程与并发(13_26)-JUC集合_ ConcurrentHashMap详解
资源推荐
资源详情
资源评论
Java
多
线
程
与
并
发
(13/26)-JUC
集
合
:
ConcurrentHashMap
详
解
JUC
集
合
:ConcurrentHashMap
详
解
JDK1.7
之
前
的
ConcurrentHashMap
使
⽤
分
段
锁
机
制
实
现
,
JDK1.8
则
使
⽤
数
组
+
链
表
+
红
⿊
树
数
据
结
构
和
CAS
原
⼦
操
作
实
现
ConcurrentHashMap
;
本
⽂
将
分别
介
绍
这
两
种
⽅
式
的
实
现
⽅
案
及
其
区
别
。
带
着
BAT
⼤
⼚
的
⾯
试
问
题
去
理
解
提
⽰
请
带
着
这
些
问
题
继续
后
⽂
,
会
很
⼤
程
度
上
帮
助
你
更
好
的
理
解
相
关
知
识
点
。
•
为什么
HashTable
慢
?
它
的
并
发
度
是
什么
?
那
么
ConcurrentHashMap
并
发
度
是
什么
?
•
ConcurrentHashMap
在
JDK1.7
和
JDK1.8
中
实
现
有
什么
差
别
?JDK1.8
解
決
了
JDK1.7
中什么
问
题
•
ConcurrentHashMapJDK1.7
实
现
的
原
理
是
什么
?
分
段
锁
机
制
•
ConcurrentHashMapJDK1.8
实
现
的
原
理
是
什么
?
数
组
+
链
表
+
红
⿊
树
,
CAS
•
ConcurrentHashMapJDK1.7
中
Segment
数
(concurrencyLevel)
默
认
值
是
多
少
?
为
何
⼀
旦
初
始
化
就
不
可
再
扩
容
?
•
ConcurrentHashMapJDK1.7
说说
其
put
的
机
制
?
•
ConcurrentHashMapJDK1.7
是
如
何
扩
容
的
?rehash(
注
:
segment
数
组
不
能
扩
容
,
扩
容
是
segment
数
组
某
个
位
置
内
部
的
数
组
HashEntry<K,V>[]
进
⾏
扩
容
)
•
ConcurrentHashMapJDK1.8
是
如
何
扩
容
的
?tryPresize
•
ConcurrentHashMapJDK1.8
链
表
转
红
⿊
树
的
时
机
是
什么
?
临
界
值
为什么
是
8?
•
ConcurrentHashMapJDK1.8
是
如
何
进
⾏
数
据
迁
移
的
?transfer
为什么
HashTable
慢
Hashtable
之
所
以
效
率
低
下主
要
是
因
为
其
实
现
使
⽤
了
synchronized
关
键
字
对
put
等
操
作
进
⾏
加
锁
,
⽽
synchronized
关
键
字
加
锁
是
对
整
个
对
象
进
⾏
加
锁
,
也
就
是
说
在
进
⾏
put
等
修
改
Hash
表
的
操
作
时
,
锁
住
了
整
个
Hash
表
,
从
⽽
使
得
其
表
现
的
效
率
低
下
。
ConcurrentHashMap-JDK1.7
在
JDK1.5~1.7
版
本
,
Java
使
⽤
了
分
段
锁
机
制
实
现
ConcurrentHashMap.
简
⽽
⾔
之
,
ConcurrentHashMap
在
对
象
中
保
存
了⼀个
Segment
数
组
,
即
将
整
个
Hash
表
划分
为
多
个
分
段
;
⽽
每
个
Segment
元
素
,
即
每
个
分
段
则
类
似
于⼀个
Hashtable
;
这
样
,
在
执
⾏
put
操
作
时
⾸
先
根
据
hash
算
法
定
位
到
元
素
属
于
哪
个
Segment
,
然
后
对
该
Segment
加
锁
即
可
。
因
此
,
ConcurrentHashMap
在
多
线
程
并
发
编
程
中
可
是
实
现
多
线
程
put
操
作
。
接
下
来
分
析
JDK1.7
版
本
中
ConcurrentHashMap
的
实
现
原
理
。
数
据
结
构
整
个
ConcurrentHashMap
由
⼀个个
Segment
组
成
,
Segment
代
表
”
部
分
“
或
”
⼀
段
“
的
意
思
,
所
以
很
多
地
⽅
都
会
将
其
描
述
为
分
段
锁
。
注
意
,
⾏
⽂
中
,
我
很
多
地
⽅
⽤
了
“
槽
”
来
代
表
⼀个
segment
。
简
单
理
解
就
是
,
ConcurrentHashMap
是
⼀个
Segment
数
组
,
Segment
通过
继
承
ReentrantLock
来
进
⾏
加
锁
,
所
以
每
次
需
要
加
锁
的
操
作
锁
住
的
是
⼀个
segment
,
这
样
只
要
保
证
每
个
Segment
是
线
程
安
全
的
,
也
就
实
现
了
全
局
的
线
程
安
全
。
concurrencyLevel
:
并
⾏
级
别
、
并
发
数
、
Segment
数
,
怎
么
翻
译
不
重
要
,
理
解
它
。
默
认
是
16
,
也
就
是
说
ConcurrentHashMap
有
16
个
Segments
,
所
以
理
论
上
,
这
个
时
候
,
最
多
可
以
同
时
⽀
持
16
个
线
程
并
发
写
,
只
要
它
们
的
操
作
分别分
布
在
不
同
的
Segment
上
。
这
个
值
可
以
在
初
始
化
的
时
候
设
置
为
其
他
值
,
但
是
⼀
旦
初
始
化
以
后
,
它
是
不
可
以
扩
容
的
。
再具
体
到
每
个
Segment
内
部
,
其
实
每
个
Segment
很
像
之
前
介
绍
的
HashMap
,
不
过
它
要
保
证
线
程
安
全
,
所
以
处
理
起
来
要
⿇
烦
些
。
初
始
化
•
initialCapacity:
初
始
容
量
,
这
个
值
指
的
是
整
个
ConcurrentHashMap
的
初
始
容
量
,
实
际
操
作
的
时
候
需
要
平
均
分
给
每
个
Segment
。
•
loadFactor:
负
载
因
⼦
,
之
前
我
们
说
了
,
Segment
数
组
不
可
以
扩
容
,
所
以
这
个
负
载
因
⼦
是
给
每
个
Segment
内
部
使
⽤
的
。
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
,
因
为
要
保
持
并
⾏
级
别
是
2
的
n
次
⽅
while (ssize < concurrencyLevel) {
++sshift;
ssize <<= 1;
}
//
我
们
这
⾥
先
不
要
那
么
烧
脑
,
⽤
默
认
值
,
concurrencyLevel
为
16
,
sshift
为
4
//
那
么
计
算
出
segmentShift
为
28
,
segmentMask
为
15
,
后
⾯
会
⽤
到
这
两个
值
this.segmentShift = 32 - sshift;
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// initialCapacity
是
设
置
整
个
map
初
始
的
⼤
⼩
,
//
这
⾥
根
据
initialCapacity
计
算
Segment
数
组
中
每
个
位
置
可
以
分到
的
⼤
⼩
//
如
initialCapacity
为
64
,
那
么
每
个
Segment
或
称
之为
"
槽
"
可
以
分到
4
个
int c = initialCapacity / ssize;
if (c * ssize < initialCapacity)
++c;
//
默
认
MIN_SEGMENT_TABLE_CAPACITY
是
2
,
这
个
值
也
是
有
讲
究
的
,
因
为
这
样
的
话
,
对
于
具
体
//
插
⼊
⼀个
元
素
不
⾄
于
扩
容
,
插
⼊
第
⼆个
的
时
候
才
会
扩
容
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c)
cap <<= 1;
//
创
建
Segment
数
组
,
//
并
创
建
数
组
的
第
⼀个
元
素
segment[0]
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
//
往
数
组
写⼊
segment[0]
UNSAFE.putOrderedObject(ss, SBASE, s0);
// ordered write of segments[0]
this.segments = ss;
}
40
41
42
43
44
初
始
化
完
成
,
我
们
得
到
了⼀个
Segment
数
组
。
我
们
就
当
是
⽤
newConcurrentHashMap()
⽆
参
构
造
函
数
进
⾏
初
始
化
的
,
那
么
初
始
化
完
成
后
:
•
Segment
数
组
⻓
度
为
16
,
不
可
以
扩
容
•
Segment[i]
的
默
认
⼤
⼩
为
2
,
负
载
因
⼦
是
0.75
,
得
出初
始
阈
值
为
1.5
,
也
就
是
以
后
插
⼊
第
⼀个
元
素
不
会
触
发
扩
容
,
插
⼊
第
⼆个
会
进
⾏
第
⼀
次
扩
容
•
这
⾥
初
始
化
了
segment[0]
,
其
他
位
置
还
是
null
,
⾄
于为什么
要
初
始
化
segment[0]
,
后
⾯
的
代
码
会
介
绍
•
当
前
segmentShift
的
值
为
32-4=28
,
segmentMask
为
16-1=15
,
姑
且
把
它
们
简
单
翻
译
为
移
位
数
和
掩
码
,
这
两个
值
⻢
上
就
会
⽤
到
put
过
程
分
析
我
们
先
看
put
的
主
流
程
,
对
于
其
中
的
⼀些
关
键
细
节
操
作
,
后
⾯
会
进
⾏
详
细
介
绍
。
public V put(K key, V value) {
Segment<K,V> s;
if (value == null)
throw new NullPointerException();
// 1.
计
算
key
的
hash
值
int hash = hash(key);
// 2.
根
据
hash
值
找
到
Segment
数
组
中
的
位
置
j
// hash
是
32
位
,
⽆
符
号右
移
segmentShift(28)
位
,
剩
下
⾼
4
位
,
//
然
后和
segmentMask(15)
做
⼀
次
与
操
作
,
也
就
是
说
j
是
hash
值
的
⾼
4
位
,
也
就
是
槽
int j = (hash >>> segmentShift) & segmentMask;
//
刚刚
说
了
,
初
始
化
的
时
候
初
始
化
了
segment[0]
,
但
是
其
他
位
置
还
是
null
,
// ensureSegment(j)
对
segment[j]
进
⾏
初
始
化
if ((s = (Segment<K,V>)UNSAFE.getObject
// nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null)
// in ensureSegment
s = ensureSegment(j);
// 3.
插
⼊
新
值
到
槽
s
中
return s.put(key, hash, value, false);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
第
⼀
层
⽪
很
简
单
,
根
据
hash
值
很
快
就
能
找
到
相
应
的
Segment
,
之
后
就
是
Segment
内
部
的
put
操
作
了
。
Segment
内
部
是
由
数
组
+
链
表
组
成
的
。
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//
在
往
该
segment
写⼊
前
,
需
要
先
获
取
该
segment
的
独
占
锁
//
先
看
主
流
程
,
后
⾯
还
会
具
体
介
绍
这
部
分
内
容
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
//
这
个
是
segment
内
部
的
数
组
HashEntry<K,V>[] tab = table;
//
再
利
⽤
hash
值
,
求
应
该
放
置
的
数
组
下
标
int index = (tab.length - 1) & hash;
// first
是
数
组
该
位
置
处
的
链
表
的
表
头
HashEntry<K,V> first = entryAt(tab, index);
//
下
⾯
这
串
for
循
环
虽
然
很
⻓
,
不
过
也
很
好
理
解
,
想想
该
位
置
没
有
任何
元
素
和
已
经
存
在
⼀个
for (HashEntry<K,V> 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 {
// node
到
底
是
不
是
null
,
这
个
要
看
获
取
锁
的
过
程
,
不
过
和
这
⾥
都
没
有
关
系
。
//
如
果
不为
null
,
那
就
直
接
将
它
设
置
为
链
表表
头
;
如
果
是
null
,
初
始
化
并
设
置
为
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
//
如
果
超
过
了
该
segment
的
阈
值
,
这
个
segment
需
要
扩
容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
//
扩
容
后
⾯
也
会
具
体
分
析
else
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
剩余23页未读,继续阅读
资源评论
weishaoonly
- 粉丝: 132
- 资源: 1383
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功