ThreadLocalMap在比其中Thread和ThreadLocal部分要复杂很多,是ThreadLocal底层存储和核心数据结构。从整体上将,ThreadLocalMap底层是Entry数组,key值为ThreadLocal的hash code, 采用线性探测法解决哈希冲突。
以下是ThreadLocalMap核心属性和方法,所有方法和属性都标识为private,仅为ThreadLocal可以访问:
ThreadLocalMap核心属性分析,主要包括底层存储数据结构,相关阈值计算,但负载因子计算貌似有点怪:
/**
* Entry[] table数组的初始大小为16,这个数值必须为2的幂次方
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 最终落地存储的数据结构是数组,其中Entry是个内部类,可以理解为key-value结构,
* 这个数组的长度必须是2的幂
*/
private Entry[] table;
/**
* 数组中实际占用了的个数
*/
private int size = 0;
/**
* 阈值,达到这个阈值,将对数组进行扩容,因此需要进行rehash
*/
private int threshold; // Default to 0
/**
* 这个方法就是设置threshold,是总长度的2/3
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
// 这些都是用来解决冲突的,说白了如果i这个位置被占了,获取i+1位置
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
以下为ThreadLocalMap的构造函数,分别是通过ThreadLocal和value值,和通过已有ThreadLocalMap构造:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 实际上就是key value对
table = new Entry[INITIAL_CAPACITY]; // 以初始化大小创建Entry数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 生成hash值,这个值为啥要选这个值?
table[i] = new Entry(firstKey, firstValue); // 设置找到的位置的值
size = 1; // 大小为1
setThreshold(INITIAL_CAPACITY); // 更新扩容阈值
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) { // 如果存在值
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); // 获取ThreadLocal
if (key != null) { // 如果为空了就应该被回收掉
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1); // 获取hash值
while (table[h] != null) // 如果这个位置已经被占用了,访问下一个位置,直到不冲突,最终的效果是循环找
h = nextIndex(h, len); // 当然是解决冲突
table[h] = c;
size++;
}
}
}
}
以下是ThreadLocalMap中核心操作方法,包括get,remove,rehash等基础实现:
private Entry getEntry(ThreadLocal<?> key) { // 根据ThreadLocal获取value值
int i = key.threadLocalHashCode & (table.length - 1); // 获取访问index
Entry e = table[i];
if (e != null && e.get() == key) // 如果找到,并且验证
return e;
else // 否者执行反向的hash的过程,其实就是从这个位置一个一个往下找
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { // 循环从i位置往下找
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key) //找到了
return e;
if (k == null) // 发现断了,擦除失效的值
expungeStaleEntry(i);
else
i = nextIndex(i, len); //当前不匹配,找下一个值
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) { // 擦除失效的节点
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null; // staleSlot这个位置失效了,置为空,size减一
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len); // 后面的逻辑就是从这个位置开始作rehash, 因为这个空了,如果后面有有效的,拿上来填上
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
private void set(ThreadLocal<?> key, Object value) { // 找到合适位置安放当前值,如果有失效的点,
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) { // 如果当前这个ThreadLocal已经有了,将新value替换之前的
e.value = value;
return;
}
if (k == null) { // 如果找到了一个key为空的位置,这个位置失效了,替换掉这个失效的位置
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) //
rehash(); // 大于threshold的0.75就重新hash
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4) // 这里有个数组的总长度,阈值,rehash的阈值,大概是总长度的2/3*0.75时才rehash
resize();
}
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2; // 扩容2倍
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) { // 重新hash
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // Help the GC,失效的值
} else {
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null) // 重新找到合适的位置
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
总结:ThreadLocalMap的实现也不是很复杂,底层为一个数组,通过ThreadLocal的threadLocalHashCode作哈希,如果发现有实效的,触发清除失效值,达到阈值触发rehash,使用线性探测法定位hash位置,即通过hash值获取数组中的一个index位置,如果已被占用就去下一个位置,如果发现失效的,触发清除失效值。但是这里的负载因子有点怪,看起来需要经过两次计算,是2/3*0.75,及0.5