1、Thread、 ThreadLocal 及 ThreadLocalMap
ThreadLocal提供了一种方式,可以让线程在操作共享变量时,复制该共享变量的一个副本到线程自己的栈空间,以后就操作这个副本空间来代替共享空间。
Thread、 ThreadLocal 及 ThreadLocalMap 三者之间的关系
每个 Thread 对象中都持有一个 ThreadLocalMap 类型的成员变量,ThreadLocalMap 自身类似于是一个 Map,里面会有一个个 key value 形式的键值对,key 就是 ThreadLocal 的引用,value是我们希望 ThreadLocal 存储的内容,例如 user 对象等。
2、ThreadLocal
2.1、ThreadLocal属性
threadLocalHashCode属性决定了ThreadLocal -> value键值对保存在线程属性ThreadLocalMap 中的桶位,桶位 = threadLocalHashCode & (table.length - 1)
public class ThreadLocal<T> {
/**
* 线程在使用ThreadLocal.get()方法时,如果是第一次在threadLocal对象上get,会给当前线程分配一个value
* 该value和ThreadLocal对象被包装成一个entry,其中key为ThreadLocal对象,value为给当前线程分配的value
* 这个entry存放到当前线程threadLocals这个map中,而entry在map中的桶位跟ThreadLocal对象的threadLocalHashCode属性值有关
* 桶位 = threadLocalHashCode & (table.length - 1)
*/
private final int threadLocalHashCode = nextHashCode();
/**
* 创建ThreadLocal对象时,给每个ThreadLocal对象分配Hash值的,每创建一个ThreadLocal对象
* 使用nextHashCode分配一个Hash值给这个对象
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* 每创建一个ThreadLocal对象,ThreadLocal.nextHashCode静态属性值会增长的大小
* 0x61c88647很特殊,是斐波拉切数也叫黄金分割数,使用该数可以使hash值分布非常均匀
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* 给新创建的ThreadLocal对象分配nextHashCode时需要调用的方法
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 赋值方法 一般会重写
* @return
*/
protected T initialValue() {
return null;
}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
public ThreadLocal() {
}
}
2.2、ThreadLocal对外方法
2.2.1、ThreadLocal#set
保存键值对,如果当前线程ThreadLocalMap属性初始化过,直接调用ThreadLocalMap#set方法覆盖或者新放入对应的键值;如果当前线程没有初始化过ThreadLocalMap属性,则直接调用ThreadLocal#createMap新建一个ThreadLocalMap,并将当前ThreadLocal对象和对应的值存入。
/**
* 修改当前线程保存的当前ThreadLocal对象的对应的value值,
*
* @param value
*/
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程对象的threadLocals属性
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果当前线程ThreadLocalMap属性初始化过,直接调用set方法覆盖或者新放入对应的键值
map.set(this, value);
} else {
//如果当前线程没有初始化过ThreadLocalMap属性,则直接新建一个ThreadLocalMap,并将当前
//ThreadLocal对象和对应的值存入
createMap(t, value);
}
}
2.2.2、ThreadLocal#get
获取当前线程保存的键值对,调用ThreadLocalMap#getEntry方法获取存储的键值对,如果获取的key在ThreadLocalMap中没有或者ThreadLocalMap没有初始化,则先初始化当前线程的ThreadLocalMap属性,然后设置获取key的默认值
/**
*返回当前线程与当前ThreadLocal对象相关联的线程局部变量 ,该变量只有当前线程能访问到
* 如果当前线程没有分配变量,则使用initialValue方法给当前线程分配一个变量
* @return
*/
public T get() {
Thread t = Thread.currentThread();
//返回当前线程的ThreadLocal.ThreadLocalMap threadLocals 属性
ThreadLocalMap map = getMap(t);
/**
* map != null 成立 说明当前线程已经初始化过ThreadLocalMap 属性了
*/
if (map != null) {
//根据当前的ThreadLocal对象去线程的ThreadLocalMap中获取value
ThreadLocalMap.Entry e = map.getEntry(this);
//e != null成立说明当前线程ThreadLocalMap中保存有对应ThreadLocal对象的value
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//返回对应的value值
return result;
}
}
/**
* 走到这里有哪些情况?
* 1、当前线程ThreadLocalMap 属性没有被初始化 需要走初始化逻辑
* 2、根据当前的ThreadLocal对象去线程的ThreadLocalMap中获取value,无对应的value
*/
return setInitialValue();
}
ThreadLocal#setInitialValue 初始化线程ThreadLocalMap,设置查询未命中的初始值
/**
*初始化当前线程对应ThreadLocal对象的初始值,如果当前线程的ThreadLocalMap对象为空,
* 初始化当前线程的ThreadLocalMap对象,
* @return
*/
private T setInitialValue() {
//调用initialValue方法为线程对应的ThreadLocal对象生成默认的value
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//如果线程内部的ThreadLocalMap属性已经初始化,则保存ThreadLocal对象 和 当前线程对应的ThreadLocal对象生成默认的value
map.set(this, value);
} else {
//如果ThreadLocalMap为空则对ThreadLocalMap初始化
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
ThreadLocal#initialValue设置默认初始值
/**
* 赋值方法 一般会重写
* @return
*/
protected T initialValue() {
return null;
}
2.2.3、ThreadLocal#remove
移除当前线程与当前ThreadLocal对象相关联的value,调用ThreadLocalMap#remove方法完成
/**
* 移除当前线程与当前ThreadLocal对象相关联的value
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
//如果线程的ThreadLocalMap属性已经初始化了,直接调用ThreadLocalMap的remove方法
m.remove(this);
}
}
3、ThreadLocalMap
3.1、ThreadLocalMap属性
ThreadLocalMap内部采用一个初始长度为16的数组保存数据,数组长度必须是2的次方,扩容阈值为散列表数组长度的2/3,扩容每次翻一倍。其向前向后访问都是循环的,第一个位置元素的上一个元素是最后一个元素,最后一个元素的上一个元素为第一个元素。
/**
* ThreadLocalMap 是用来存储 线程对应的ThreadLocal的值的自定义hash Map,
*
*/
static class ThreadLocalMap {
/**
* Entry 结构实际上是继承了一个ThreadLocal类型的弱引用并将其作为 key
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
/**
* 初始化ThreadLocalMap时散列表数组的长度 默认16
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 散列表数组 数组长度必须是2的次方,扩容每次翻一倍
*/
private Entry[] table;
/**
* ThreadLocalMap的数据数量即散列表数组的占用情况
*/
private int size = 0;
/**
* 扩容触发阈值 初始值为len * 2 / 3
* 触发之后调用rehash()方法
* rehash()方法会先做一次全量的过期数据检查,把散列表中所有的过期entry全部删除
* 如果移除之后当前散列表的entry数量任然达到threshold * 3 /4,就进行扩容
*/
private int threshold; // Default to 0
/**
* 设置扩容阈值为散列表数组长度的2/3
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 后一个元素位置(循环)
* 参数1:当前下标
* 参数2:当前散列表数组长度
*
* 如果i到达散列表数组最后一个元素位置下一步就变为0,访问数组的第一个元素,循环访问数组
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 前一个元素位置(循环访问)
* 参数1:当前下标
* 参数2:当前散列表数组长度
* 如果i为0,则上一个元素的位置为n-1,为数组最后一个元素
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/**
* 构造一个包含第一个entry的ThreadLocalMap
* @param firstKey 第一个键值对的key
* @param firstValue 第一个键值对的value
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//默认初始长度16
table = new Entry[INITIAL_CAPACITY];
//计算桶位
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//将第一个键值对放入散列表数组
table[i] = new Entry(firstKey, firstValue);
//更改size为1
size = 1;
//设置扩容阈值
setThreshold(INITIAL_CAPACITY);
}
/**
* 根据现有的ThreadLocalMap 构建一个新的ThreadLocalMap
* @param parentMap 父ThreadLocalMap
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
}
3.2、ThreadLocalMap对外方法
3.2.1 ThreadLocalMap#set
特别注意ThreadLocalMap没有采用拉链法解决hash冲突,而是采用开放地址法,如果发送hash冲突,则向后查找最靠近的桶位。首先通过ThreadLocal对象的threadLocalHashCode属性计算桶位,桶位 = threadLocalHashCode & (table.length - 1),存放键值对时可能有多种情况:
1、通过key计算出的桶位上没有数据,直接赋值
2、通过key计算出的桶位上有数据,但是键值对的key和要设置的key相同,说明此时为重新赋值
3、发生hash冲突,继续向后遍历,找到entry的key等于要放入数据的key的位置,说明此时为重新赋值
4、发生hash冲突,继续向后遍历,找到entry的key为null,过期数据的位置,此时调用ThreadLocalMap#replaceStaleEntry替换过期数据
5、发生hash冲突,继续向后遍历,找到一个没有存放entry的位置,size +1,调用ThreadLocalMap#cleanSomeSlots进行启发式清理工作,如果没有数据被清理,并且数据量已经超过了扩容阈值,则调用ThreadLocalMap#rehash进行扩容
/**
* 调用set方法给当前线程添加ThreadLocal->value键值对
* 调用ThreadLocal的set方法时实际调用的ThreadLocalMap的set方法
* @param key
* @param value
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//计算桶位 即entry存放在散列表数组的下标
int i = key.threadLocalHashCode & (len-1);
/**
* for循环从当前桶位向后遍历 如果发生hash冲突,则继续向后遍历,直到找到一个可用的位置
* 1、找到一个没有存放entry的位置
* 2、找到entry的key等于要放入数据的key的位置(相同key元素重新赋值)
* 3、找到entry的key为null,过期数据的位置
*
* 如果计算的桶位上没有entry则证明没有发生hash冲突,不执行while循环
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//执行while循环证明发生了hash冲突或者是统一key重新赋值
ThreadLocal<?> k = e.get();
//当前桶位的key和要放入键值对的key相同 则证明为value重赋值
if (k == key) {
//value赋值 返回
e.value = value;
return;
}
//如果key为null,则证明key关联的ThreadLocal对象已经被回收了,该位置数据未过期数据
if (k == null) {
//替换过期数据
replaceStaleEntry(key, value, i);
return;
}
}
/**
* 执行到这里说明 当前位置entry为null,创建新的entry对象
*/
tab[i] = new Entry(key, value);
//size + 1
int sz = ++size;
/***
* 进行启发式清理工作,如果没有数据被清理,并且数据量已经超过了扩容阈值,则进行扩容
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold){
rehash();
}
}
3.2.2、ThreadLocalMap#getEntry
根据ThreadLocal对象从当前线程取出对应的value
/**
* 获取ThreadLocal对象关联的value
*
* ThreadLocal对象的get()方法实际调用的ThreadLocalMap的getEntry方法
* @param key ThreadLocal对象
* @return
*/
private Entry getEntry(ThreadLocal<?> key) {
//计算entry的位置 获取数据对应散列表数组的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果对应位置的值不为空 并且对应entry的key为所查找的key 则证明该位置的entry就是要查找的entry 直接返回
if (e != null && e.get() == key)
return e;
else{
/**
* 执行到这里有几种情况?
* 1、当前key 查找到散列表数据对应位置数据为空
* 2、发生了hash冲突,当前key查询到的entry的key和要查询的key不一致,需要向当前桶位后查询(ThreadLocalMap是通过开放地址法解决hash冲突的,并未使用拉链法)
*/
return getEntryAfterMiss(key, i, e);
}
}
调用getEntry方法时,未获取到对应entry的逻辑
/**
* 通过key未查询到entry的处理逻辑
* 1、通过key未查询到entry
* 2、通过key查询到的entry的key和指定的key不同,发生了hash冲突
* @param key ThreadLocal对象
* @param i 通过查询的key计算出的桶位 即散列表数组的下标
* @param e 查询出来的entry
* 1、为空则说明 通过key未查询到entry
* 2、不为空则说明 发生了hash冲突,通过key查询到的entry的key和指定的key不同,entry指向查出的冲突的entry
* @return
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//从发生hash冲突的entry循环向后访问
while (e != null) {
ThreadLocal<?> k = e.get();
//k == key 说明找到了要查询的entry
if (k == key)
return e;
/**
* 元素的key为null(因为ThreadLocalMap的key为弱引用,如果key关联的ThreadLocal对象被GC回收了,那么key就会为null)
* 说明当前位置的entry的key关联的ThreadLocal对象被GC回收了
*/
if (k == null){
//当散列表出现过期数据了 需要做一次探测式的过期数据检查和回收
expungeStaleEntry(i);
}
//没有找到对应的entry 继续向后循环查找
else
i = nextIndex(i, len);
//e为下一个元素
e = tab[i];
}
/**
* 此处有两种情况
* 1、传入的e就为空,没有执行while循环,通过key未查询到entry,直接返回null
* 2、传入的e不为空,通过key查询到的entry的key和指定的key不同,发生了hash冲突,执行了while循环,但是
* 循环向后访问对象时,直到查询到entry为null,也没没找对应的entry(ThreadLocalMap采用开放地址法解决hash冲突,、
* 一旦发生hash冲突,就会将元素放在冲突位置后的最近可用位置),则返回null
*/
return null;
}
3.2.3、ThreadLocalMap#remove
/**
* 删除key对应的entry
*/
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//计算桶位
int i = key.threadLocalHashCode & (len-1);
//从计算出的位置循环查找到entry为null的位置 如果找到entry的key和当前待删除的key相同 在清除该entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//清理当前的entry
e.clear();
//执行一次探测式的清理
expungeStaleEntry(i);
return;
}
}
}
3.3、ThreadLocalMap内部核心方法
3.3.1、ThreadLocalMap#replaceStaleEntry
插入数据时,如果key计算出的位置发现被占用了,则从当前位置向后查找,如果找到entry的key为null的位置,则会调用当前方法替换过期数据。
其中主要逻辑为: 前驱扫描 获取最前面的一个过期数据的下标(不一定最小,是循环访问的),以初始值为key计算出的桶位staleSlot向后迭代,寻找过期数据,直到碰到entry为null结束,获取第一个过期数据的下标位置。如果向后查找过程中,找到了和要查询键值对key相同的entry,证明为重新赋值操作,将键值对放到之前entry失效的位置;如果向后查找过程中并未找到key和待插入key相同的entry,说明当前set操作为添加逻辑,直接将失效数据位置重新赋值。
其中向前和向后遍历时,遇到过期数据会修改slotToExpunge的值,当遇到过期数据时或者向后查询找到了和要查询键值对key相同的entry时,都需要调用
ThreadLocalMap#expungeStaleEntry方法进行一次探测式清理,然后调用ThreadLocalMap#cleanSomeSlots方法执行一次启发式清理。
/**
* 替换过期Entry(插入数据时,如果key计算出的位置发现被占用了,则从当前位置向后查找,找到一个entry为null的位置
* 或者entry的key为null的位置 或者 entry的key为待插入key的位置,如果entry的key为null的位置,则会调用当前方法替换过期数据)
* @param key 待插入键值对的key
* @param value 待插入键值对的value
* @param staleSlot key计算出的桶位即散列数组的下标
*/
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//探测式清理过期数据的下标 初始值为 key计算出的桶位即散列数组的下标staleSlot
int slotToExpunge = staleSlot;
/**
* 前驱扫描 获取最前面的一个过期数据的下标(不一定最小,是循环访问的),赋值给slotToExpunge
* 以初始值为key计算出的桶位staleSlot向前迭代,寻找过期数据,直到碰到entry为null结束
* int i = prevIndex(staleSlot, len); i的初始值为key计算出的桶位的前一个位置
* (e = tab[i]) != null; 循环条件是entry不为null
*/
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len)){
//如果当前位置为过期数据,更新探测式清理过期数据的下标为i
if (e.get() == null){
slotToExpunge = i;
}
}
/**
* 以初始值为key计算出的桶位staleSlot向后迭代,寻找过期数据,直到碰到entry为null结束
* 向后扫描 获取第一个过期数据的下标位置赋值给slotToExpunge
*/
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
/**
* 如果entry的key和待插入的key一样,说明是替换逻辑
* 直接将
*/
if (k == key) {
//更新新的value
e.value = value;
//将过期数据tab[staleSlot] 放到 当前循环到的位置i上
tab[i] = tab[staleSlot];
//将过期数据位置数据tab[staleSlot]保存为更新了的当前entry
tab[staleSlot] = e;
//slotToExpunge == staleSlot 成立说明一开始向前查找过期数据时,并未找到过期的entry,向后扫描时,也未发现过期数据
if (slotToExpunge == staleSlot){
//将探测式清理过期数据的下标修改为当前循环的下标
slotToExpunge = i;
}
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
/**
* 条件1: k == null 说明遍历的entry是一个过期数据
* 条件2:slotToExpunge == staleSlot说明前驱扫描时未发现过期数据
*
*/
if (k == null && slotToExpunge == staleSlot){
//向后遍历找到过期数据了,并且前驱扫描时未发现过期数据,更新探测式清理过期数据的下标为i
slotToExpunge = i;
}
}
/**
* 运行到这里的条件
* 向后查找过程中并未找到key和待插入key相同的entry,说明当前set操作为添加逻辑
*/
//将原过期数据的value设置为null
tab[staleSlot].value = null;
//将新插入键值对添加到staleSlot中
tab[staleSlot] = new Entry(key, value);
// slotToExpunge != staleSlot 成立说明 除了staleSlot以外还发现其他过期的数据了
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
3.3.2、ThreadLocalMap#expungeStaleEntry
探测式清理主要是清理了过期的数据以及hash冲突优化
1、从staleSlot这个过期数据位置开始向后查找过期数据,直到对应位置entry为null为止,遍历过程中,如果entry的key为null,则将entry的value设置为null,将当前位置的entry设置为空,size-1
2、遍历过程中对entry的key不为null的数据,需要重新计算桶位,如果是发生hash冲突的,需要将其移到最靠近正确位置的可用位置
/**
* 探测式的过期数据回收以及hash冲突优化
* 1、从staleSlot这个过期数据位置开始向后查找过期数据,直到对应位置entry为null为止,遍历过程中,如果entry的key为null,
* 则将entry的value设置为null,将当前位置的entry设置为空,size-1
* 2、遍历过程中对entry的key不为null的数据,需要重新计算桶位,如果是发生hash冲突的,需要将其移到最靠近正确位置的可用位置
*
* 当获取元素时发现,有entry的key为null,即散列表有过期数据时需要调用该方法
* @param staleSlot entry的key为null的散列表数组下标
* @return 返回从staleSlot这个过期数据位置开始向后查找最后一个不为空数据的下标
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将当前位置的entry的value设置为null
tab[staleSlot].value = null;
//将当前位置的entry设置为空
tab[staleSlot] = null;
//减少一个元素 size-1
size--;
// 当前遍历的entry
Entry e;
//当前遍历的下标
int i;
/**
* i = nextIndex(staleSlot, len) i的初始值为staleSlot的下一个元素位置,因为staleSlot位置已经处理过了
* 循环的条件是 (e = tab[i]) != null 即下一个位置的entry不为null
* i = nextIndex(i, len) 向后循环遍历
*
*/
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
//获取当前遍历entry的key
ThreadLocal<?> k = e.get();
//如果key为空,则证明为过期数据,当前entry的key关联的ThreadLocal对象已经被gc回收了,做回收处理
if (k == null) {
// 将entry的value设置为null
e.value = null;
//将当前位置的entry设置为空
tab[i] = null;
//减少一个元素 size-1
size--;
} else {
//key不为空 则证明数据不为空
//根据key的hashcode重新计算下 桶位
int h = k.threadLocalHashCode & (len - 1);
/**
* 如果计算的桶位和当前entry的桶位不同,则证明插入数据时发生了hash冲突,此时需要优化
* 将数据移到离正确桶位更近的位置
*/
if (h != i) {
//将当前桶位的数据设置为null
tab[i] = null;
//从entry应该在的正确位置向后查找,循环条件是对应位置的entry不为null,也就是最多查询到 当前循环处理过期数据的位置 i
while (tab[h] != null)
h = nextIndex(h, len);
//将数据放到离数据应该在的正确桶位最靠近的位置
tab[h] = e;
}
}
}
return i;
}
3.3.3、ThreadLocalMap#cleanSomeSlots
启发式清理,返回是否清理了过期数据的标识
/**
* 启发式清理
* @param i 启发式清理的开始位置
* @param n 当前散列表数组的长度
* @return
*/
private boolean cleanSomeSlots(int i, int n) {
//启发式清理是否清除过过期数据
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//获取下一个位置
i = nextIndex(i, len);
Entry e = tab[i];
//如果当前位置的数据过期了
if (e != null && e.get() == null) {
n = len;
//启发式清理是否清除过过期数据标记为true
removed = true;
//以当前过期的位置做一次探测式清理工作
i = expungeStaleEntry(i);
}
//循环一次 n无符号右移一位
} while ( (n >>>= 1) != 0);
return removed;
}
3.3.4、ThreadLocalMap#cleanSomeSlots
清理所有的过期数据,如果清理后,元素数量还大于散列数组长度的3/4 则调用ThreadLocalMap#resize方法进行扩容
/**
* 清理所有的过期数据,如果清理后,元素数量还大于散列数组长度的3/4 则进行扩容
*/
private void rehash() {
//该方法遍历每个桶位进行探测式清理 执行完后当前散列表内所有过期数据都会被移除
expungeStaleEntries();
// 如果清理所有的过期数据后 size还大于散列数组长度的3/4 则进行扩容
if (size >= threshold - threshold / 4)
resize();
}
3.3.5、ThreadLocalMap#resize
每次双倍扩容,创建一个双倍长度的数组循环迁移数据
/**
* 双倍的扩容
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
//创建了一个双倍长度的数组
Entry[] newTab = new Entry[newLen];
int count = 0;
//循环迁移数据
for (Entry e : oldTab) {
if (e != null) {
ThreadLocal<?> k = e.get();
//k == null成立 说明为过期数据, 过期数据不用迁移
if (k == null) {
e.value = null; // Help the GC
} else {
//重新计算新的桶位
int h = k.threadLocalHashCode & (newLen - 1);
//hash时拿到最近的位置
while (newTab[h] != null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
//更新扩容阈值
setThreshold(newLen);
size = count;
table = newTab;
}