ConcurrentHashMap分析Word文件下载.docx

上传人:b****5 文档编号:18197946 上传时间:2022-12-14 格式:DOCX 页数:16 大小:24.32KB
下载 相关 举报
ConcurrentHashMap分析Word文件下载.docx_第1页
第1页 / 共16页
ConcurrentHashMap分析Word文件下载.docx_第2页
第2页 / 共16页
ConcurrentHashMap分析Word文件下载.docx_第3页
第3页 / 共16页
ConcurrentHashMap分析Word文件下载.docx_第4页
第4页 / 共16页
ConcurrentHashMap分析Word文件下载.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

ConcurrentHashMap分析Word文件下载.docx

《ConcurrentHashMap分析Word文件下载.docx》由会员分享,可在线阅读,更多相关《ConcurrentHashMap分析Word文件下载.docx(16页珍藏版)》请在冰豆网上搜索。

ConcurrentHashMap分析Word文件下载.docx

Segment<

K,V>

[] 

segments;

不变(Immutable)和易变(Volatile)

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。

如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,读操作不加锁将得到不一致的数据。

ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。

HashEntry代表每个hash链中的一个节点,其结构如下所示:

static 

class 

HashEntry<

key;

int 

hash;

volatile 

value;

next;

5.} 

可以看到除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除节点,因为这需要修改next引用值,所有的节点的修改只能从头部开始。

对于put操作,可以一律添加到Hash链的头部。

但是对于remove操作,可能需要从中间删除一个节点,这就需要将要删除节点的前面所有节点整个复制一遍,最后一个节点指向要删除结点的下一个结点。

这在讲解删除操作时还会详述。

为了确保读操作能够看到最新的值,将value设置成volatile,这避免了加锁。

其它

为了加快定位段以及段中hash槽的速度,每个段hash槽的的个数都是2^n,这使得通过位运算就可以定位段和段中hash槽的位置。

当并发级别为默认值16时,也就是段的个数,hash值的高4位决定分配在哪个段中。

但是我们也不要忘记《算法导论》给我们的教训:

hash槽的的个数不应该是2^n,这可能导致hash槽分配不均,这需要对hash值重新再hash一次。

这是重新hash的算法,还比较复杂,我也懒得去理解了。

Java代码

1.private 

hash(int 

h) 

// 

Spread 

bits 

to 

regularize 

both 

segment 

and 

index 

locations, 

using 

variant 

single-word 

Wang/Jenkins 

hash. 

+= 

(h 

<

15) 

0xffffcd7d;

5. 

^= 

>

10);

6. 

3);

7. 

6);

8. 

2) 

14);

9. 

return 

16);

10.} 

这是定位段的方法:

1.final 

segmentFor(int 

hash) 

segments[(hash 

segmentShift) 

&

segmentMask];

3.} 

数据结构

关于Hash表的基础数据结构,这里不想做过多的探讨。

Hash表的一个很重要方面就是如何解决hash冲突,ConcurrentHashMap和HashMap使用相同的方式,都是将hash值相同的节点放在一个hash链中。

与HashMap不同的是,ConcurrentHashMap使用多个子Hash表,也就是段(Segment)。

下面是ConcurrentHashMap的数据成员:

1.public 

ConcurrentHashMap<

K, 

V>

extends 

AbstractMap<

implements 

ConcurrentMap<

 

Serializable 

Mask 

value 

for 

indexing 

into 

segments. 

upper 

key'

code 

are 

used 

choose 

the 

segment. 

segmentMask;

10. 

Shift 

within 

segments. 

11. 

12. 

segmentShift;

13. 

14. 

15. 

16. 

17. 

18.} 

所有的成员都是final的,其中segmentMask和segmentShift主要是为了定位段,参见上面的segmentFor方法。

每个Segment相当于一个子Hash表,它的数据成员如下:

ReentrantLock 

2.private 

long 

serialVersionUID 

2249069246763182397L;

number 

elements 

in 

this 

segment'

region. 

transient 

count;

Number 

updates 

that 

alter 

size 

table. 

This 

is 

during 

bulk-read 

methods 

make 

sure 

they 

see 

consistent 

snapshot:

If 

modCounts 

change 

traversal 

segments 

computing 

or 

checking 

containsValue, 

then 

we 

might 

have 

an 

inconsistent 

view 

state 

so 

(usually) 

must 

retry. 

modCount;

18. 

19. 

table 

rehashed 

when 

its 

exceeds 

threshold. 

20. 

(The 

field 

always 

tt>

(int)(capacity 

21. 

loadFactor)<

/tt>

.) 

22. 

23. 

threshold;

24. 

25. 

26. 

per-segment 

table. 

27. 

28. 

table;

29. 

30. 

31. 

load 

factor 

Even 

though 

value 

32. 

same 

all 

it 

replicated 

avoid 

needing 

33. 

links 

outer 

object. 

34. 

@serial 

35. 

36. 

float 

loadFactor;

37.} 

count用来统计该段数据的个数,它是volatile,它用来协调修改和读取操作,以保证读取操作能够读取到几乎最新的修改。

协调方式是这样的,每次修改操作做了结构上的改变,如增加/删除节点(修改节点的值不算结构上的改变),都要写count值,每次读取操作开始都要读取count的值。

这利用了Java5中对volatile语义的增强,对同一个volatile变量的写和读存在happens-before关系。

modCount统计段结构改变的次数,主要是为了检测对多个段进行遍历过程中某个段是否发生改变,在讲述跨段操作时会还会详述。

threashold用来表示需要进行rehash的界限值。

table数组存储段中节点,每个数组元素是个hash链,用HashEntry表示。

table也是volatile,这使得能够读取到最新的table值而不需要同步。

loadFactor表示负载因子。

实现细节

修改操作

先来看下删除操作remove(key)。

remove(Object 

key) 

hash(key.hashCode());

segmentFor(hash).remove(key, 

hash, 

null);

4.} 

整个操作是先定位到段,然后委托给段的remove操作。

当多个删除操作并发进行时,只要它们所在的段不相同,它们就可以同时进行。

下面是Segment的remove方法实现:

1.V 

key, 

Object 

value) 

lock();

try 

count 

1;

tab 

(tab.length 

1);

first 

tab[index];

first;

while 

(e 

!

null 

(e.hash 

|| 

key.equals(e.key))) 

e.next;

oldValue 

null;

if 

null) 

e.value;

(value 

== 

value.equals(v)) 

v;

All 

entries 

following 

removed 

node 

can 

stay 

list, 

but 

preceding 

ones 

need 

be 

cloned. 

++modCount;

newFirst 

(HashEntry<

e;

p.next) 

new 

(p.key, 

p.hash, 

newFirst, 

p.value);

tab[index] 

newFirst;

c;

write-volatile 

oldValue;

finally 

unlock();

33.} 

整个操作是在持有段锁的情况下执行的,空白行之前的行主要是定位到要删除的节点e。

接下来,如果不存在这个节点就直接返回null,否则就要将e前面的结点复制一遍,尾结点指向e的下一个结点。

e后面的结点不需要复制,它们可以重用。

下面是个示意图,我直接从这个网站上复制的(画这样的图实在是太麻烦了,如果哪位有好的画图工具,可以推荐一下)。

删除元素之前:

删除元素3之后:

第二个图其实有点问题,复制的结点中应该是值为2的结点在前面,值为1的结点在后面,也就是刚好和原来结点顺序相反,还好这不影响我们的讨论。

整个remove实现并不复杂,但是需要注意如下几点。

第一,当要删除的结点存在时,删除的最后一步操作要将count的值减一。

这必须是最后一步操作,否则读取操作可能看不到之前对段所做的结构性修改。

第二,remove执行的开始就将table赋给一个局部变量tab,这是因为table是volatile变量,读写volatile变量的开销很大。

编译器也不能对volatile变量的读写做任何优化,直接多次访问非volatile实例变量没有多大影响,编译器会做相应优化。

接下来看put操作,同样地put操作也是委托给段的put方法。

下面是段的put方法:

put(K 

value, 

boolean 

onlyIfAbsent) 

(c++ 

threshold) 

ensure 

capacity 

rehash();

(!

onlyIfAbsent) 

e.value 

else 

(key, 

first, 

value);

30.} 

该方法也是在持有段锁的情况下执行的,首先判断是否需要rehash,需要就先rehash。

接着是找是否存在同样一个key的结点,如果存在就直接替换这个结点的值。

否则创建一个新的结点并添加到hash链的头部,这时一定要修改modCount和count的值,同样修改count的值一定要放在最后一步。

put方法调用了rehash方法,reash方法实现得也很精巧,主要利用了table的大小为2^n,这里就不介绍了。

修改操作还有putAll和replace。

putAll就是多次调用put方法,没什么好说的。

replace甚至不用做结构上的更改,实现要比put和delete要简单得多,理解了put和delete,理解replace就不在话下了,这里也不介绍了。

获取操作

首先看下get操作,同样ConcurrentHashMap的get操作是直接委托给Segment的get方法,直接看Segment的get方法:

get(Object 

(count 

0) 

read-volatile 

getFirst(hash);

key.equals(e.key)) 

(v 

null) 

readValueUnderLock(e);

recheck 

15.} 

get操作不需要锁。

第一步是访问count变量,这是一个volatile变量,由于所有的修改操作在进行结构修改时都会在最后一步写count变量,通过这种机制保证get操作能够得到几乎最新的结构更新。

对于非结构更新,也就是结点值的改变,由于HashEntry的value变量是volatile的,也能保证读取到最新的值。

接下来就是对hash链进行遍历找到要获取的结点,如果没有找到,直接访回null。

对hash链进行遍历不需要加锁的原因在于链指针next是final的。

但是头指针却不是final的,这是通过getFirst(hash)方法返回,也就是存在table数组中的值。

这使得getFirst(hash)可能返回过时的头结点,例如,当执行get方法时,刚执行完getFirst(hash)之后,另一个线程执行了删除操作并更新头结点,这就导致get方法中返回的头结点不是最新的。

这是可以允许,通过对count变量的协调机制,get能读取到几乎最新的数据,虽然可能不是最新的。

要得到最新的数据,只有采用完全的同步。

最后,如果找到了所求的结点,判断它的值如果非空就直接返回,否则在有锁的状态下再读一次。

这似乎有些费解,理论上结点的值不可能为空,这是因为put的时候就进行了判断,如果为空就要抛NullPointerException。

空值的唯一源头就是HashEntry中的默认值,因为HashEntry中

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 初中教育 > 政史地

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1