从当前节点staleSlot向后查找key值相等的Entry元素,找到后更新Entry的值并交换staleSlot元素的位置(staleSlot位置为过期元素),更新Entry数据,然后开始进行过期Entry的清理工作,如下图所示:
Yu4oWT.png向后遍历过程中,如果没有找到相同key值的Entry数据:
YuHMee.png从当前节点staleSlot向后查找key值相等的Entry元素,直到Entry为null则停止寻找。通过上图可知,此时table中没有key值相同的Entry。
创建新的Entry,替换table[stableSlot]位置:
YuH3FA.png替换完成后也是进行过期元素清理工作,清理工作主要是有两个方法:expungeStaleEntry()和cleanSomeSlots(),具体细节后面会讲到,请继续往后看。
ThreadLocalMap.set()源码详解上面已经用图的方式解析了set()实现的原理,其实已经很清晰了,我们接着再看下源码:
java.lang.ThreadLocal.ThreadLocalMap.set():
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) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
这里会通过key来计算在散列表中的对应位置,然后以当前key对应的桶的位置向后查找,找到可以使用的桶。
Entry[] tab = table;int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
什么情况下桶才是可以使用的呢?
k = key 说明是替换操作,可以使用
碰到一个过期的桶,执行替换逻辑,占用过期桶
查找过程中,碰到桶中Entry=null的情况,直接使用
接着就是执行for循环遍历,向后查找,我们先看下nextIndex()、prevIndex()方法实现:
YZSC5j.png 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);
}
接着看剩下for循环中的逻辑:
遍历当前key值对应的桶中Entry数据为空,这说明散列数组这里没有数据冲突,跳出for循环,直接set数据到对应的桶中
如果key值对应的桶中Entry数据不为空
2.1 如果k = key,说明当前set操作是一个替换操作,做替换逻辑,直接返回
2.2 如果key = null,说明当前桶位置的Entry是过期数据,执行replaceStaleEntry()方法(核心方法),然后返回
for循环执行完毕,继续往下执行说明向后迭代的过程中遇到了entry为null的情况
3.1 在Entry为null的桶中创建一个新的Entry对象
3.2 执行++size操作
调用cleanSomeSlots()做一次启发式清理工作,清理散列数组中Entry的key过期的数据
4.1 如果清理工作完成后,未清理到任何数据,且size超过了阈值(数组长度的2/3),进行rehash()操作
4.2 rehash()中会先进行一轮探测式清理,清理过期key,清理完成后如果size >= threshold - threshold / 4,就会执行真正的扩容逻辑(扩容逻辑往后看)