ConcurrentHashMap源码阅读02
前言
在上一篇我们介绍了ConcurrentHashMap的常量、存储单元及成员变量。今天我们就结合测试案例来探究一下ConcurrentHashMap中put方法的实现原理。
正文
建立测试案例
建立测试案例如下:
@Test
public void test(){
Map<String,Object> map = new ConcurrentHashMap<String,Object>();
map.put("a",1);
}
我们发现在对ConcurrentHashMap内的节点数组进行初始化时,会用到U.compareAndSwapInt方法,那我们就先看看U:
UnSafe mechanics
// Unsafe mechanics
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;
static {
try {
U = sun.misc.Unsafe.getUnsafe();
Class<?> k = ConcurrentHashMap.class;
SIZECTL = U.objectFieldOffset
(k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex"));
BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset
(k.getDeclaredField("cellsBusy"));
Class<?> ck = CounterCell.class;
CELLVALUE = U.objectFieldOffset
(ck.getDeclaredField("value"));
Class<?> ak = Node[].class;
ABASE = U.arrayBaseOffset(ak);
int scale = U.arrayIndexScale(ak);
if ((scale & (scale - 1)) != 0)
throw new Error("data type scale not a power of two");
ASHIFT = 31 - Integer.numberOfLeadingZeros(scale);
} catch (Exception e) {
throw new Error(e);
}
}
这个静态代码块对上面未初始化的变量进行了初始化:
U,通过UnSafe的获取唯一实例的方法进行初始化
U.objectFieldOffset方法获取的是对应字段在ConcurrentHashMap类的内存中相对于该类首地址的偏移量。
SIZECTL,对应ConcurrentHashMap类的sizeCtl字段;
TRANSFERINDEX ,对应transferIndex字段;
BASECOUNT ,对应baseCount字段;
CELLSBUSY,对应cellsBusy字段;
CELLVALUE,对应CounterCell类的value字段;
U.arrayBaseOffset方法可以获取数组第一个元素的偏移地址。
ABASE,获取以Node[]为元素的数组的第一个元素的偏移地址;
U.arrayIndexScale方法可以获取数组的转换因子,也就是数组中元素的增量地址。将arrayBaseOffset与arrayIndexScale配合使用,可以定位数组中每个元素在内存中的位置。
scale ,获取以Node[]为元素的数组的转换因子。
Integer.numberOfLeadingZeros方法的作用是返回无符号整型i的最高非零位前面的0的个数,包括符号位在内;
如果i为负数,这个方法将会返回0,符号位为1.
比如说,10的二进制表示为 0000 0000 0000 0000 0000 0000 0000 1010
java的整型长度为32位。那么这个方法返回的就是28
ASHIFT ,31减去scale最高非零位前面的0的个数后的结果。
调试分析
进入put方法:
/**
* Maps the specified key to the specified value in this table.
* Neither the key nor the value can be null.
*
* <p>The value can be retrieved by calling the {@code get} method
* with a key that is equal to the original key.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with {@code key}, or
* {@code null} if there was no mapping for {@code key}
* @throws NullPointerException if the specified key or value is null
*/
public V put(K key, V value) {
return putVal(key, value, false);
}
在这个table中,指定的key对应指定的value。key和value都不可以为null.
使用与原key相同的key作为参数,调用get方法可以获取对应的value.
接下来,我们进入putVal方法:
/** Implementation for put and putIfAbsent */
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;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,