文章目录
-
- 1. 简介
- 2. 继承体系
- 3. 字段
- 4. 构造器
- 5. 常用方法
1. 简介
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>implements ConcurrentMap<K,V>, Serializable {
}
ConcurrentHashMap 是线程安全的 HashMap 版本,并且它采用分段锁的思想实现了线程安全,其效率比通过全局锁实现线程安全的 HashTable 高得多。
JDK 1.7 及以前,ConcurrentHashMap 底层采用 segment 数组 + HsahEntry 数组来存储元素,JDK 1.8 开始采用 数组 + 链表/红黑树 来存储元素;ConcurrentHashMap 采用的锁有 synchronized,CAS,自旋锁,分段锁,volatile 等。
ConcurrentHashMap 中没有 threshold(阈值等于 容量(数组长度) * 加载因子)和 loadFactor 这两个字段,而是采用 sizeCtl 来控制:
- sizeCtl = -1,表示有线程正在进行初始化操作;
- sizeCtl = 0,默认值,表示后续在真正初始化的时候使用默认容量;
- sizeCtl > 0,在初始化之前存储的是传入的容量,在初始化或扩容后存储的是下一次的扩容门槛;
- sizeCtl = (resizeStamp << 16) + (1 + nThreads),表示有n个线程正在一起扩容,高位存储扩容邮戳,低位存储扩容线程数加 1
ConcurrentHashMap 与 HashMap 的区别 ?线程安全方面;底层数据结构,分版本。
ConcurrentHashMap 与 HashTable 的区别 ?HashTable 使用全局锁对整张哈希表加锁让线程独占,效率低下,而 ConcurrentHashMap 使用分段锁思想,对数组上的每一个桶都加了锁,各个桶可以并发操作,效率非常高。
2. 继承体系
3. 字段
4. 构造器
-
public ConcurrentHashMap() { }
-
public ConcurrentHashMap(int initialCapacity) { if (initialCapacity < 0)throw new IllegalArgumentException();int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?MAXIMUM_CAPACITY :tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));this.sizeCtl = cap; }
-
public ConcurrentHashMap(Map<? extends K, ? extends V> m) { this.sizeCtl = DEFAULT_CAPACITY;putAll(m); }
-
public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, 1); }
-
//concurrencyLevel 并发数,默认是 16 public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0)throw new IllegalArgumentException();if (initialCapacity < concurrencyLevel) // Use at least as many binsinitialCapacity = concurrencyLevel; // as estimated threadslong size = (long)(1.0 + (long)initialCapacity / loadFactor);int cap = (size >= (long)MAXIMUM_CAPACITY) ?MAXIMUM_CAPACITY : tableSizeFor((int)size);this.sizeCtl = cap; }
5. 常用方法
- public V put(K key, V value):如果桶数组未初始化,则初始化;如果待插入的元素所在的桶为空,则尝试把此元素直接插入到桶的第一个位置;如果正在扩容,则当前线程一起加入到扩容的过程中;如果待插入的元素所在的桶不为空且不在迁移元素,则锁住这个桶(分段锁);如果当前桶中元素以链表方式存储,则在链表中寻找该元素或者插入元素;如果当前桶中元素以红黑树方式存储,则在红黑树中寻找该元素或者插入元素;如果元素存在,则返回旧值 (并没有替换旧值);如果元素不存在,整个Map的元素个数加1,并检查是否需要扩容。添加元素操作中使用的锁主要有(自旋锁 + CAS + synchronized + 分段锁)
- public V remove(Object key):删除元素跟添加元素一样,都是先找到元素所在的桶,然后采用分段锁的思想锁住整个桶,再进行操作:计算hash;如果所在的桶不存在,表示没有找到目标元素,返回;如果正在扩容,则协助扩容完成后再进行删除操作;如果是以链表形式存储的,则遍历整个链表查找元素,找到之后再删除;如果是以树形式存储的,则遍历树查找元素,找到之后再删除,删除元素之后树较小,则退化成链表;如果确实删除了元素,则整个map元素个数减1,并返回旧值;如果没有删除元素,则返回null。
- public V get(Object key):获取元素,根据目标key所在桶的第一个元素的不同采用不同的方式获取元素,关键点在于find()方法的重写:hash到元素所在的桶;如果桶中第一个元素就是该找的元素,直接返回;如果是树或者正在迁移元素,则调用各自Node子类的find()方法寻找元素;如果是链表,遍历整个链表寻找元素。获取元素的过程没有加锁
- public int size():元素个数的存储也是采用分段的思想,获取元素个数时需要把所有段加起来:元素的个数依据不同的线程存在在不同的段里;计算CounterCell所有段及baseCount的数量之和。获取元素个数的过程也没有加锁