Java——ConcurrentMap 解读
来源:互联网 发布:昊腾淘宝没有卖的吗 编辑:程序博客网 时间:2024/06/08 15:07
Java集合框架中的Map类型的数据结构是非线程安全, 在多线程环境中使用时需要手动进行线程同步. 因此在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构: ConcurrentMap. 本篇文章主要关注ConcurrentMap接口以及它的Hash版本的实现ConcurrentHashMap.
要实现线程安全,就需要加锁, HashTable就是线程安全的, 但是HashTable对整张表加锁的做法非常消耗性能, ConcurrentMap的做法简单来说, 就是把哈希表分成若干段, 对其中某一段操作时, 只锁住这一段, 其他段可以不受影响. 如下图所示:
整个ConcurrentMap由一个segment数组组成(即segments), 数组中每一个segment是一张哈希表, 哈希表中存放的是一张hashentry链表.
本文主要分析以下两个过程:
1.ConcurrentMap的构造方法
2.put(K key, V value)
ConcurrentMap的构造方法
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel);public ConcurrentHashMap(int initialCapacity, float loadFactor);public ConcurrentHashMap(int initialCapacity);public ConcurrentHashMap();public ConcurrentHashMap(Map<? extends K, ? extends V> m);
这5种构造方法, 最终都会使用第一个构造函数来初始化ConcurrentHashMap.
第一个构造函数的代码:
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; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } this.segmentShift = 32 - sshift; 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<K,V> s0 = new Segment<K,V>(loadFactor, (int)(cap * loadFactor), (HashEntry<K,V>[])new HashEntry[cap]); Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize]; UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0] this.segments = ss;}
其中concurrencyLevel是实例化以后就不可改变的, 它决定了segments数组的大小, 同时决定了这个ConcurrentHashMap的并发能力.segmentShift和segmentMask与hash算法相关.loadFactor参数决定了Segment在何时扩容.
ConcurrentHashMap的put方法
public V put(K key, V value) { Segment<K,V> s; if (value == null) throw new NullPointerException(); int hash = hash(key); int j = (hash >>> segmentShift) & segmentMask; if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment s = ensureSegment(j); return s.put(key, hash, value, false);}
过程如下:
1.通过第一次哈希算法,计算当前key属于哪一个segment;
2.如果映射到的segment为空, 则执行3, 否则执行4;
3.借助UNSAFE类, 使用线程安全的方式, 构建一个新的segment, 并且放入segments中相应的位置;
4.调用segment中的put方法, 将key, value对放入segment中.
从中可以看到, 最终存储key,value对的是segment, 因此只要对需要插入键值对的segment上锁就可以保证线程安全.
segment的put方法
首先看segment的定义, 它是ConcurrentHashMap的内部类, 该类继承了ReentrantLock.
static final class Segment<K,V> extends ReentrantLock implements Serializable {}
查看它的构造方法:
Segment(float lf, int threshold, HashEntry<K,V>[] tab) { this.loadFactor = lf; this.threshold = threshold; this.table = tab;}
Segment的主要构成是一张哈希表(存储hashentry)和threshold(当哈希表尺寸大于threshold时扩容).
下面是它的put方法:
final V put(K key, int hash, V value, boolean onlyIfAbsent) { HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value); V oldValue; try { HashEntry<K,V>[] tab = table; int index = (tab.length - 1) & hash; HashEntry<K,V> first = entryAt(tab, index); 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 { if (node != null) node.setNext(first); else node = new HashEntry<K,V>(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;}
put方法的主要流程如下:
1.尝试获取当前segment的锁,若成功则执行3, 若失败则执行2;
2.根据key在hashtable中搜索hashentry, 并且获取锁(此处会进行阻塞操作);
3.根据key在hashtable中搜索hashentry,如果当前位置已经存在值, 则使用链接的方式解决冲突;
4.如果当前尺寸超过了threshold,则执行rehash(将segment中的所有键值对重新hash).
总结
ConcurrentHashMap的实现,主要原理就是使用segments将原本唯一的hashtable分段, 增加并发能力. ConcurrentHashMap中还有比较重要的一点就是使用的两次哈希算法(第一次哈希算法找到segment, 第二次哈希算法找到hashentry), 这两次哈希算法要求能尽量将元素均匀的分布到不同的segment的hashtable中, ConcurrentHashMap使用的是single-word Wang/Jenkins哈希算法的一个变种。
- Java——ConcurrentMap 解读
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
- 《深入浅出 Java Concurrency》—并发容器 ConcurrentMap
- Java ConcurrentMap 源码分析
- Java并发包:ConcurrentMap
- Java SynchronizedMap ConcurrentMap ConcurrentHashMap
- Java并发包:ConcurrentMap
- java--集合--concurrentMap(1.7)
- concurrentMap
- concurrentMap
- concurrentMap
- ConcurrentMap
- [Java] ConcurrentMap 分析和思考
- 【Java】8.ConcurrentMap与ConcurrentHashMap
- 深入浅出 Java Concurrency : 并发容器ConcurrentMap
- 关于位操作的总结
- 【codeforces 761A】Dasha and Stairs
- 【codeforces 761B】Dasha and friends
- Labview2016安装及破解步骤
- JDBC——java中数据库的应用
- Java——ConcurrentMap 解读
- mysql 5.7.13 安装配置方法图文教程(linux)
- 【codeforces 761C】Dasha and Password(动态规划做法)
- Gzip压缩
- 瑞安led发光字制作
- whole-genome-sequencing Data Analysis 学习笔记1 基本概念
- 开发工具使用:eclipse+git
- Codeforces761B. Dasha and friends (KMP)
- 设计模式之单例模式