1.ThreadLocalMap内部结构
static class ThreadLocalMap {
/*
* 这里的Entry继承了弱引用,(弱引用只要发生GC就要被回收)
* Entry的key(ThreadLocal)以弱引用的方式指向ThreadLocal对象。
* (可以避免内存泄露)
* (参考 https://blog.csdn.net/qq_46312987/article/details/117166100)
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//Entry数组初始长度,数组的长度必然是2的次方数
private static final int INITIAL_CAPACITY = 16;
//Entry数组
private Entry[] table;
//Entry数组中的元素个数
private int size = 0;
/*
* 扩容阈值 初始值为 len * 2/3
* 触发后调用 rehash()方法
* rehash() 方法先做一次全局检查过期数据,把散列表中所有过期的entry移除
* 如果移除之后 当前散列表中的entry个数仍然达到 阈值的3/4,就进行扩容。
*/
private int threshold;
//设置阈值为当前数组长度的2/3
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/*
* @param i 当前下标
* @param len 数组长度
* 返回当前位置的下一个位置
*/
private static int nextIndex(int i, int len) {
/*
* 当前下标+1 如果小于数组长度的话 返回 +1的值
* 如果等于数组长度的话,就返回0,即形成了环
*/
return ((i + 1 < len) ? i + 1 : 0);
}
/*
* 跟nextIndex()方法类似。返回当前位置的上一个位置
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
/*
* 构造方法
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//构造一个初始长度为16的Entry数组
table = new Entry[INITIAL_CAPACITY];
//寻址,(hash值 & length - 1)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//构造一个Entry对象放到指定的位置上
table[i] = new Entry(firstKey, firstValue);
//size = 1
size = 1;
//设置阈值为初始化容量(16)的2 / 3 = 10
setThreshold(INITIAL_CAPACITY);
}
}
2.getEntry()方法详解
//----------------------------ThreadLocal.get()-------------------------------
// 此方法详解在 [https://blog.csdn.net/qq_46312987/article/details/121799343]
// 这里我们详细讲解 getEntry()方法。
public T get() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程内部的threadLcoalMap
ThreadLocalMap map = getMap(t);
//内部的map不为NULL,
if (map != null) {
//这里调用了getEntry(),去threadLocalMap中查询指定的数据
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
//-----------------------------ThreadLocalMap.getEntry()---------------------
/*
* ThreadLocal对象的get()操作实际上是由ThreadLocalMap.getEntry()完成的。
*/
private Entry getEntry(ThreadLocal<?> key) {
//寻址
int i = key.threadLocalHashCode & (table.length - 1);
//获取指定位置的Entry
Entry e = table[i];
/*
* 当前位置不为NULL 并且当前位置的key(ThreadLocal对象)与传来的key是否一致
* 注意 ThreadLocalMap与HashMap或者是ConcurrentHashMap最大的区别就是
* 当出现hash冲突时使用的是开放寻址法(找下一个位置)而不是拉链法(在当前位置形成 * 链表)
*/
if (e != null && e.get() == key)
return e;
else
//在当前位置没找到就去后面继续找。
return getEntryAfterMiss(key, i, e);
}
//-----------------------ThreadLocalMap.getEntryAfterMiss()-------------------
/*
* @param key ThreadLocal对象
* @param i 第一次寻址的索引位置
* @param e table第i个位置的entry对象
*
* 此方法的作用就是从当前位置向后查询,查询到指定数据返回,当查询到某一个位置的 * Entry为NULL时结束,最终返回NULL,在查询过程中如果某一个位置的entry不为NULL,
* 但是key为NULL,说明对应的当前entry关联的ThreadLcoal对象已经被回收了,那么就 * 会将当前的entry清理掉
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//向后查找如果还没有查找到 但是当前位置的entry为NUll,说明查找不到,结束循环
while (e != null) {
//entry继承了弱引用,get()方法就是获取内部的ThreadLocal对象
ThreadLocal<?> k = e.get();
//查找成功,返回entry。
if (k == key)
return e;
/* key是NULL,说明entry关联的ThreadLocal被回收了,但是entry还存在,
* 这时就需要将当前位置的entry干掉。
*/
if (k == null)
expungeStaleEntry(i);
//k不为NULL,但是当前entry不是目标entry,继续向后查找
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
//--------------------------ThreadLocalMap.expungeStaleEntry()----------------
/*
* @param staleSlot (stale翻译为过期的) 此方法的作用
* 1.即当前位置上的entry的key是NULL,说明当前的entry已经没有用了。
* 需要将其干掉。
* 2.遍历哈希表,将从当前位置开始的entry != null && key == null的所有entry * 干掉,然后将正常的entry做一次重新迁移,优化查询。
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//将当前位置的entry的的value置为NULL(help GC)
tab[staleSlot].value = null;
//将当前位置置为NULL,将entry直接干掉
tab[staleSlot] = null;
//size - 1
size--;
//下面就是rehash()的过程
//当前遍历的entry
Entry e;
//当前的位置
int i;
//循环遍历数组(从置为NULL的entry对象的下一个位置开始),直到某一位置上的entry为NULL为止。
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
/*
* 能进入for循环,说明当前位置的entry一定不为NULL。
*/
ThreadLocal<?> k = e.get();
//k(ThreadLocal对象) = NULL,将当前entry的value和当前entry全部干掉。
if (k == null) {
e.value = null;
tab[i] = null;
size --;
} else {
/*
* 执行到这里,说明当前位置的entry不为NULL。
* 此时需要执行的事就是判断当前位置上的entry是否在经过哈希寻址后应 * 该在的位置,(因为有可能发生过冲突),如果不在该在的位置,就去寻找
* 距离寻址位置最近的位置(也可能找到寻址的位置)。
*/
//重新计算索引
int h = k.threadLocalHashCode & (len - 1);
//条件成立 表示当前entry确实不在该在的位置,需要尝试重新找位置存放。
if (h != i) {
tab[i] = null;
//循环找位置存放
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
3.getEntry()运行流程图
4.set()方法详解
//-----------------ThreadLocalMap.set()---------------------------------------
private void set(ThreadLocal<?> key, Object value) {
//寻址
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
/*
* for循环做的事就是,循环寻找key相同的entry。
* 1.找到相同key并且正常的entry,做value替换
* 2.找到某一位置(entry != null && entry.key == NULL),将entry替换。
*/
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//找到了相同的key,替换value。
if (k == key) {
e.value = value;
return;
}
//查找过程中,碰到了entry.key == null,说明当前entry是过期数据
if (k == null) {
//替换过期的entry。
replaceStaleEntry(key, value, i);
return;
}
}
/*
* 执行到这里,说明for循环找到了一个当前slot为NULL的情况,
* 此时直接在这个slot位置上创建一个Entry对象。
*/
tab[i] = new Entry(key, value);
int sz = ++size;
/*
* 这里做一次清理工作,cleanSomeSlots()返回true表示内部没有清理到数据
* 这时在判断元素数量是否达到了扩容阈值,大于等于阈值就进行rehash()操作
*/
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//----------------------ThreadLocalMap.replaceStaleEntry----------------------
/*
* 替换过期的entry。
* @param key 新key
* @param value 新value
* @param staleSlot 过期entry的位置
*/
private void replaceStaleEntryreplaceStaleEntry
(ThreadLocal<?> key, Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//将过期entry的位置赋值给slotToExpunge
int slotToExpunge = staleSlot;
/*
* 以当前staleSlot位置的前一个位置开始,向前迭代查找,
* (结束条件entry = null),更新slotToExpunge为靠前的
* (entry != null && entry.key == null) 的过期entry的位置。
*/
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len)){
if (e.get() == null){
slotToExpunge = i;
}
}
/*
* 从当前不合法的entry的位置的下一个位置开始遍历,
*
*/
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
//获取当前位置的entry的key(threadLocal)
ThreadLocal<?> k = e.get();
/*
* key要添加的新key
* k 当前遍历的key
* k == key 说明要添加的key已经存在了,需要替换value
* 然后做清理逻辑
*/
if (k == key) {
//替换value。
e.value = value;
//将过期的entry放到当前位置i,因为下面要从i这个位置开始清理
tab[i] = tab[staleSlot];
//将替换完毕的entry放到过期数据的位置
tab[staleSlot] = e;
//条件成立:说明replaceStaleEntry一开始时向前查找过期数据时,并未 //找到过期的entry.
if (slotToExpunge == staleSlot)
//因为上面做了交换,所以当前位置i就是过期数据,赋值给slotToExpunge
slotToExpunge = i;
//下面就是清理过期的entry。
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
/*
* 条件1: k == null成立,说明当前遍历的entry是一个过期数据
* 条件2: slotToExpunge == staleSlot成立说明一开始向前查找过期数据 * 并未找到过期的entry
*/
if (k == null && slotToExpunge == staleSlot)
//因为向后查询过程中查找到了一个过期数据,更新slotToExpunge为当前位 //置,前提条件是前驱扫描时未发现过期数据
slotToExpunge = i;
}
/*
* 什么时候执行到这里?
*—>向后查找过程中,并未发现 key = null 的entry,说明当前set操作是一个添加
* 逻辑,直接将新数据添加到过期entry的位置上。
*/
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
//条件成立:说明除了当前staleSlot过期entry位置以外,还发现其他的过期slot了
if (slotToExpunge != staleSlot)
//开启清理数据的逻辑。
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
//------------------ThreadLocalMap.cleanSomeSlots----------------------------
/*
* @param i 表示清理工作的起始位置,这个位置一定是NULL。
* @param n 表示table.length
*/
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//获取当前i位置的下一个位置
i = nextIndex(i, len);
//获取位置上的entry。
Entry e = tab[i];
/*
* 条件成立表示当前位置的entry是过期数据,
* 需要清理
*/
if (e != null && e.get() == null) {
n = len;
//清理表示置为true,表示清理过
removed = true;
//以当前过期的slot位置开始,做一次探测式清理工作。
i = expungeStaleEntry(i);
}
/*
* 表示循环次数。
*/
} while ( (n >>>= 1) != 0);
return removed;
}
5.set()方法流程图
6.rehash()方法
只有当前table元素个数大于等于
扩容阈值并且在清理完table内部的所有过期的entry后,元素个数还大于等于阈值的3/4
,这时才会触发扩容。
//--------------------ThreadLocalMap.rehash()---------------------------------
private void rehash() {
//这个方法执行完毕后,当前散列表内部所有过期的数据,都会被干掉。
expungeStaleEntries();
//条件成立,说明清理完所有的过期entry后,size数量仍然达到了扩容阈值的 3/4
//才会去做一次resize()扩容。
if (size >= threshold - threshold / 4)
resize();
}
//------------------------ThreadLocalMap.resize()-----------------------------
private void resize() {
//扩容,变为原长度的2倍
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2; // 新长度 = 原长度 * 2;
//创建一个新的table,
Entry[] newTab = new Entry[newLen];
//表示新表中的元素个数
int count = 0;
//遍历原表中的每一个slot,将原表中的数据迁移到新表。
for (int j = 0; j < oldLen; ++j) {
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);
//遍历找空位置(找到距离目标位置最近的一个slot)
while (newTab[h] != null)
h = nextIndex(h, newLen);
//放到新位置
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen); //设置新的扩容阈值
size = count; // 将count赋值给size
table = newTab; //将新表赋值给table。
}
//----------------------ThreadLcoalMap.remove()-------------------------------
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//根据key获取索引位置
int i = key.threadLocalHashCode & (len - 1);
//遍历
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//找到指定key
if (e.get() == key) {
/*
* entry是弱引用,调用clear()方法会将内部关联的threadLocal置为 * NULL
*/
e.clear();
//清理当前位置 将entry内部的value以及entry干掉。
expungeStaleEntry(i);
return;
}
}
}