concurrencyLevel 参数在现代实现中没有任何作用,它的存在只是为了保持 API 与早期版本的 JDK 的兼容性,或者如 Javadoc 所说:
此外,为了与此类的先前版本兼容,构造函数可以选择指定预期的concurrencyLevel 作为内部大小调整的附加提示。
如下implemented:
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 bins
initialCapacity = concurrencyLevel; // as estimated threads
long size = (long)(1.0 + (long)initialCapacity / loadFactor);
int cap = (size >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)size);
this.sizeCtl = cap;
}
也就是说,concurrencyLevel 仅在用户指定不切实际的低 initialCapacity 时覆盖 initialCapacity。就是这样。
此外,这只是设置地图的初始容量;实际容量会随着条目数量的增加而增加(由loadFactor 指示)。
总之,concurrencyLevel 除了最晦涩的用例(一个初始容量指定不明确的映射,包含的条目比访问它的线程少,而且由于所有线程花费大部分时间而引起激烈竞争)与该地图进行交互)。
你还问过:
由于每个段本质上是一个锁,肯定更多的段会减慢程序的速度,因为这意味着某些方法和操作必须等待?
要获取或放置一个值,线程只会锁定包含它的段。也就是说,将映射划分为更多的段不会导致每次操作都获取或释放更多的锁,但会降低锁争用的概率,因为每个段的条目更少。
另外,值得注意的是,ConcurrentHashMap 中的许多方法都是免等待的。例如,下面是获取值的方法:
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = tab.length) > 0 &&
(e = tabAt(tab, (n - 1) & h)) != null) {
if ((eh = e.hash) == h) {
if ((ek = e.key) == key || (ek != null && key.equals(ek)))
return e.val;
}
else if (eh < 0)
return (p = e.find(h, key)) != null ? p.val : null;
while ((e = e.next) != null) {
if (e.hash == h &&
((ek = e.key) == key || (ek != null && key.equals(ek))))
return e.val;
}
}
return null;
}