ConsurrentHashMap InitTable 疑问与思考
ConsurrentHashMap 作为单机下经常使用的一个线程安全Map值得我们学习一下,以下其初始化的过程的代码,以及学习过程中遇到的疑惑与自己的思考
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
// 此时有其他线程正在初始化,礼让
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
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;
}
1、多线程下该方法不加锁时如何保证线程安全的
通常意义下的加锁,都是去检查一个东西是否被“其他”持有,如果被持有则本次加锁尝试失败。这里也是同样的思路,通过判断sc是否小于零来判断当前是否有其他线程操作,如果有其他线程操作到下面sc会被修改成**-1**来表示被加锁,同时CAS的操作保证了只能由一个线程修改成功,即只有一个线程能获得锁
。
2、为什么使用while 而不是if
initTable 的方法是为了完成初始化返回一个node数组,所以没有**“获取锁”线程需要陷入等待,等待其他线程完成初始化,所以该方法使用了while来让没有“获取锁”**的线程不断循环等待。
3、为什么使用Thread.yield(),而不是return、sleep
第二个问题说到需要循环等待所有自然不可能提前return,那为什么不用sleep,因为我们并不知道另一个线程什么时候完成初始化,也就不知道sleep多少秒,多了、少了都不合适,所以干脆就让出cpu时间片,等下一次再看看,如果其他线程完成了初始化就返回,否则重复以上。这其实就是源码中注释表达的含义:lost initialization race; just spin,退出竞争,不断自旋。