1.设计思路
ConcurrentHashMap采用了分段锁(segment)的设计,只有在一个分段内才存在竞争关系,不同分段锁之间没有锁的竞争。分段锁的设计大大增强了ConcurrentHashMap的并发能力。
private static final int DEFAULT_CONCURRENCY_LEVEL = 16;//默认并发度=16可自行设置
并发度可以理解为程序运行时能够同时更新ConcurrentHashMap且不产生锁竞争的最大线程数,也就是分段锁的数量。默认为16,但用户可以在构造函数中自己定义并发度。
并发度过大 | 并发度过小 |
---|---|
原本位于同一个Segment的访问会扩散到不同的Segment中,CPU cache的命中率会下降,从而引起程序性能的下降 | 会带来严重的锁竞争问题 |
2.继承关系
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> implements ConcurrentMap<K,V>, Serializable
源码所示,ConcurrentHashMap继承字AbstarctMap,并且实现了ConcurrentMap和Serializable接口;这表明该类可以支持多线程安全访问,也可以实现序列化将类永久保存。另外,此类与HashTable的行为大致相同,不允许null键或null值
3.重要参数和构造方法
private static final int MAXIMUM_CAPACITY = 1 << 30; //最大容量 2^30
private static final int DEFAULT_CAPACITY = 16; //初始容量 16
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大散列表长度
private static final float LOAD_FACTOR = 0.75f; //负载因子 0.75
这里说明的是,所有hash表的负载因子都是0.75f,为什么是0.75而不是1或者0.5 ?
因为散列表扩容的条件是 元素个数>=容量 X 负载因子,若取负载因子 = 1时,只有当散列表填满才扩容,当负载因子 = 0.5 时,在散列表只装填一半的时候就扩容,这样非常浪费空间,所以取了一个折中的数字 0.75;
/* -----------------结点---------------------- */
//用final修饰表示节点一旦确定就不可在改变
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //hash值
final K key; //键
volatile V val; //值
volatile Node<K,V> next; //下一个结点的指针
//volatile保证在多线程的情况下实现数据的可见性
//node的构造函数 初始化结点
Node(int hash, K key, V val, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.val = val;
this.next = next;
}
//equals方法
public final boolean equals(Object o) {
Object k, v, u; //因为key,val不一定是基本类型,还有可能是封装类 所以使用object
Map.Entry<?,?> e;// ?表示占位符
/*
*返回 传入的o与entry类型相同,key相同且val相同
*/
return ((o instanceof Map.Entry) &&
(k = (e = (Map.Entry<?,?>)o).getKey()) != null &&
(v = e.getValue()) != null &&
(k == key || k.equals(key)) &&
(v == (u = val) || v.equals(u)));
}
//find方法 找出需要查找的结点n hash(n)=h n.val=k
Node<K,V> find(int h, Object k) {
Node<K,V> e = this;
if (k != null) {
do {
K ek;
if (e.hash == h &&
((ek = e.key) == k || (ek != null && k.equals(ek))))
return e;
} while ((e = e.next) != null);
}
return null;
}
构造函数:
ConcurrentHashMap()
//创建一个带有默认初始容量 (16)、加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
ConcurrentHashMap(int initialCapacity)
//创建一个带有指定初始容量、默认加载因子 (0.75) 和 concurrencyLevel (16) 的新的空映射。
ConcurrentHashMap(int initialCapacity, float loadFactor)
//创建一个带有指定初始容量、加载因子和默认 concurrencyLevel (16) 的新的空映射。
ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel)
//创建一个带有指定初始容量、加载因子和并发级别的新的空映射。
ConcurrentHashMap(Map<? extends K,? extends V> m)
//构造一个与给定映射具有相同映射关系的新映射。
3.三个常用的方法
3.1. get(Object key) 返回指定键所映射到的值,如果此映射不包含该键的映射关系,则返回 null。
public V get(Object key) {
//定义参数 并赋值 tab 散列表
Node<K,V>[] tab;
Node<K,V> e, p;
int n, eh;
K ek;
//通过hash函数拿到hash值
int h = spread(key.hashCode());
//让tab=table拿到散列表 判断其不为空 长度大于0
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
//如果 eh = h
if ((eh = e.hash) == h) {
// 如果 ek=key 返回 val
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
// 如果 eh<0 判断找到的p是否不等于null 如果不等于表示找到p对应的映射值
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
//否则在链表上找下一个结点 一直遍历下去 找到则返回值 否则返回null
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}
3.2. put(K key,V val) 将指定键映射到此表中的指定值。
final V putVal(K key, V value, boolean onlyIfAbsent) {
//如果传入的值不符合要求 抛出异常 ConcurrentHashMap不运行出现null
if (key == null || value == null) throw new NullPointerException();
//拿到hash
int hash = spread(key.hashCode());
int binCount = 0;
//将hash表取出来给tab 开启死循环 何时成功插入 何时退出
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
//当tab=null或length=0时 tab初始化且线程让步
if (tab == null || (n = tab.length) == 0)
tab = initTable();
//根据hash值计算出在table中的位置
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果该位置没有值,放进去之后 直接跳出
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//同步代码块开始锁 锁住的是当前要操作的结点f
synchronized (f) {
if (tabAt(tab, i) == f) {
/*
* tabAt(tab,i) CAS检测 且为final函数
* 保证所有的元素在resize时都具备准确性和唯一性
*/
if (fh >= 0) {
binCount = 1;
//开始链表的遍历
for (Node<K,V> e = f;; ++binCount) {
K ek;
//如果找到结点 则更新新值
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
//如果找不到结点 则将新节点加入到链表的尾部
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
/*
* 这里的TreeBin是一个红黑树的根节点 ;
* TreeBin作为红黑树的根节点,让其自己完成
* 红黑树的自平衡操作
*/
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
//在类树结构上添加结点
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
//如果链表长度已经达到了8 就需要把链表转化为树结构
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
//当前COncurrentHashMap的元素数量+1
addCount(1L, binCount);
return null;
}
3.3. size() 返回此映射中的键-值映射关系数。
public int size() {
//计算n的大小
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
//CounterCell 用于计数的一个类的一张表 可以理解为map的计数器 提供给同步增删的CAS操作
//CAS是一个常用的乐观锁机制,通过比较目标值与新值是否相同,来修改旧值到目标值 会产生ABA问题
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}