目录
前言
我们知道,在日常开发中使用的 HashMap
是线程不安全的,而线程安全类 HashTable
和 SynchronizedMap
只是简单的在方法上加锁实现了线程安全,效率低下,所以在线程安全的环境下我们通常会使用 ConcurrentHashMap
,那么 ConcurrentHashMap
又是如何实现线程安全的呢?
ConcurrentHashMap
是如何实现线程安全的
针对这个问题,可以从以下几个方面来阅读源码予以解答
初始化数据结构时的线程安全
在 JDK 1.8
中,初始化 ConcurrentHashMap
的时候这个 Node[]
数组是还未初始化的,会等到第一次 put()
方法调用时才初始化
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 判断Node数组为空
if (tab == null || (n = tab.length) == 0)
// 初始化Node数组
tab = initTable();
......
}
此时会有并发问题的,如果多个线程同时调用 initTable()
初始化 Node[]
数组怎么办?看看 Doug Lea
大师是如何处理的
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
// 每次循环都获取最新的Node[]数组引用
while ((tab = table) == null || tab.length == 0) {
// sizeCtl是一个标记位,若为-1,代表有线程在进行初始化工作了
if ((sc = sizeCtl) < 0)
// 让出CPU时间片
Thread.yield(); // lost initialization race; just spin
// CAS操作,将本实例的sizeCtl变量设置为-1
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
ConcurrentHashMap
源码中 sizeCtl
属性注释如下
// 表初始化和调整控件大小。如果为负值,则表正在初始化或调整大小:
// -1用于初始化,否则-(1+活动调整大小线程的数量)
// 否则,当table为null时,将保留创建时使用的初始表大小,默认值为0。初始化后,保存下一个要调整表大小的元素计数值
private transient volatile int sizeCtl;