Java并发编程之ConcurrentHashMap

上传人:野鹰 文档编号:3037041 上传时间:2017-07-30 格式:DOCX 页数:27 大小:177.67KB
返回 下载 相关 举报
Java并发编程之ConcurrentHashMap_第1页
第1页 / 共27页
Java并发编程之ConcurrentHashMap_第2页
第2页 / 共27页
Java并发编程之ConcurrentHashMap_第3页
第3页 / 共27页
Java并发编程之ConcurrentHashMap_第4页
第4页 / 共27页
Java并发编程之ConcurrentHashMap_第5页
第5页 / 共27页
点击查看更多>>
资源描述

《Java并发编程之ConcurrentHashMap》由会员分享,可在线阅读,更多相关《Java并发编程之ConcurrentHashMap(27页珍藏版)》请在金锄头文库上搜索。

1、Java 并发编程之 ConcurrentHashMapConcurrentHashMapConcurrentHashMap 是一个线程安全的 Hash Table,它的主要功能是提供了一组和 HashTable 功能相同但是线程安全的方法。ConcurrentHashMap 可以做到读取数据不加锁,并 且其内部的结构可以让其在进行写操作的时候能够将锁的粒度保持地尽量地小,不用对整个 ConcurrentHashMap 加锁。ConcurrentHashMap 的内部结构ConcurrentHashMap 为了提高本身的并发能力,在内部采用了一个叫做Segment 的结构,一个 Segment

2、 其实就是一个类 Hash Table 的结构,Segment 内部维护了一个链表数组,我们用下面这一幅图来看下ConcurrentHashMap 的内部结构:从 上面的结构我们可以了解到,ConcurrentHashMap 定位一个元素的过程需要进行两次 Hash 操作,第一次 Hash 定位到 Segment,第二 次 Hash 定位到元素所在的链表的头部,因此,这一种结构的带来的副作用是 Hash 的过程要比普通的 HashMap 要长,但是带来的好处是写操作的时候可 以只对元素所在的 Segment 进行加锁即可,不会影响到其他的 Segment,这样,在最理想的情况下,Concurr

3、entHashMap 可以最高同 时支持 Segment 数量大小的写操作(刚好这些写操作都非常平均地分布在所有的 Segment 上),所以,通过这一种结 构,ConcurrentHashMap 的并发能力可以大大的提高。Segment我们再来具体了解一下 Segment 的数据结构:Java 代码 1. static final class Segment extends ReentrantLock implements Serializable 2. transient volatile int count; 3. transient int modCount; 4. transient

4、 int threshold; 5. transient volatile HashEntry table; 6. final float loadFactor; 7. 详细解释一下 Segment 里面的成员变量的意义: count:Segment 中元素的数量 modCount:对 table 的大小造成影响的操作的数量(比如 put 或者 remove 操作) threshold:阈值,Segment 里面元素的数量超过这个值依旧就会对 Segment 进行扩容 table:链表数组,数组中的每一个元素代表了一个链表的头部 loadFactor:负载因子,用于确定 thresholdHa

5、shEntrySegment 中的元素是以 HashEntry 的形式存放在链表数组中的,看一下HashEntry 的结构:Java 代码 1. static final class HashEntry 2. final K key; 3. final int hash; 4. volatile V value; 5. final HashEntry next; 6. 可以看到 HashEntry 的一个特点,除了 value 以外,其他的几个变量都是 final的,这样做是为了防止链表结构被破坏,出现 ConcurrentModification 的情况。ConcurrentHashMap

6、的初始化下面我们来结合源代码来具体分析一下 ConcurrentHashMap 的实现,先看下初始化方法:Java 代码 1. public ConcurrentHashMap(int initialCapacity, 2. float loadFactor, int concurrencyLevel) 3. if (!(loadFactor 0) | initialCapacity MAX_SEGMENTS) 7. concurrencyLevel = MAX_SEGMENTS; 8. 9. / Find power-of-two sizes best matching arguments

7、10. int sshift = 0; 11. int ssize = 1; 12. while (ssize MAXIMUM_CAPACITY) 21. initialCapacity = MAXIMUM_CAPACITY; 22. int c = initialCapacity / ssize; 23. if (c * ssize (cap, loadFactor); 31. CurrentHashMap 的初始化一共有三个参数,一个 initialCapacity,表示初始的容量,一个 loadFactor,表示负载参数,最后一个是 concurrentLevel,代表Concurren

8、tHashMap 内部的 Segment 的数量,ConcurrentLevel 一经指定,不可改 变,后续如果 ConcurrentHashMap 的元素数量增加导致ConrruentHashMap 需要扩容,ConcurrentHashMap 不会 增加 Segment 的数量,而只会增加 Segment 中链表数组的容量大小,这样的好处是扩容过程不需要对整个 ConcurrentHashMap 做 rehash,而只需要对 Segment 里面的元素做一次 rehash 就可以了。整 个 ConcurrentHashMap 的初始化方法还是非常简单的,先是根据concurrentLeve

9、l 来 new 出 Segment,这里 Segment 的数量是不大于concurrentLevel 的最大的 2 的指数,就是说 Segment 的数量永远是 2 的指数个,这样的好处是方便采用移位 操作来进行 hash,加快 hash 的过程。接下来就是根据 intialCapacity 确定 Segment 的容量的大小,每一个 Segment 的容量大小 也是 2 的指数,同样是为了加快 hash 的过程。这 边需要特别注意一下两个变量,分别是 segmentShift 和 segmentMask,这两个变量在后面将会起到很大的作用,假设构造函数确定了 Segment 的数量是 2

10、的 n 次方,那么 segmentShift 就等于 32 减去 n,而 segmentMask 就等于2 的 n 次方减一。ConcurrentHashMap 的 get 操作前面提到过 ConcurrentHashMap 的 get 操作是不用加锁的,我们这里看一下其实现:Java 代码 1. public V get(Object key) 2. int hash = hash(key.hashCode(); 3. return segmentFor(hash).get(key, hash); 4. 看第三行,segmentFor 这个函数用于确定操作应该在哪一个 segment 中进行

11、,几乎对 ConcurrentHashMap 的所有操作都需要用到这个函数,我们看下这个函数的实现:Java 代码 1. final Segment segmentFor(int hash) 2. return segments(hash segmentShift) & segmentMask; 3. 这 个函数用了位操作来确定 Segment,根据传入的 hash 值向右无符号右移segmentShift 位,然后和 segmentMask 进行与操作,结合 我们之前说的segmentShift 和 segmentMask 的值,就可以得出以下结论:假设 Segment 的数量是 2 的 n

12、 次方,根据元素的 hash 值 的高 n 位就可以确定元素到底在哪一个 Segment 中。在确定了需要在哪一个 segment 中进行操作以后,接下来的事情就是调用对应的 Segment 的 get 方法:Java 代码 1. V get(Object key, int hash) 2. if (count != 0) / read-volatile 3. HashEntry e = getFirst(hash); 4. while (e != null) 5. if (e.hash = hash & key.equals(e.key) 6. V v = e.value; 7. if (v

13、 != null) 8. return v; 9. return readValueUnderLock(e); / recheck 10. 11. e = e.next; 12. 13. 14. return null; 15. 先看第二行代码,这里对 count 进行了一次判断,其中 count 表示 Segment 中元素的数量,我们可以来看一下 count 的定义:Java 代码 1. transient volatile int count; 可以看到 count 是 volatile 的,实际上这里里面利用了 volatile 的语义: 写道对 volatile 字段的写入操作 ha

14、ppens-before 于每一个后续的同一个字段的读操作。因为实际上 put、remove 等操作也会更新 count 的值,所以当竞争发生的时候,volatile 的语义可以保证写操作在读操作之前,也就保证了写操作对后续的读操作都是可见的,这样后面 get 的后续操作就可以拿到完整的元素内容。然后,在第三行,调用了 getFirst()来取得链表的头部:Java 代码 1. HashEntry getFirst(int hash) 2. HashEntry tab = table; 3. return tabhash & (tab.length - 1); 4. 同样,这里也是用位操作来确

15、定链表的头部,hash 值和 HashTable 的长度减1 做与操作,最后的结果就是 hash 值的低 n 位,其中 n 是 HashTable 的长度以 2 为底的结果。在 确定了链表的头部以后,就可以对整个链表进行遍历,看第 4 行,取出 key对应的 value 的值,如果拿出的 value 的值是 null,则可能这个 key,value 对正在 put 的过程中,如果出现这种情况,那么就加锁来保证取出的 value 是完整的,如果不是 null,则直接返回 value。ConcurrentHashMap 的 put 操作看完了 get 操作,再看下 put 操作,put 操作的前面也是确定 Segment 的过程,这里不再赘述,直接看关键的 segment 的 put 方法:Java 代码 1. V put(K

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号