相关文章:
ThreadLocalMap 是 ThreadLocal 的内部类,是一个自定义的哈希映射,用于维护线程的局部变量;其方法的访问权限都是私有的,因此只有 ThreadLocal 可以调用 ThreadLocalMap 中的方法,而其他类则不可以;此外其类访问权限是包级别的,也就是同一个包下面的类可以访问 ThreadLocalMap,但是不能调用 ThreadLocalMap 中的方法
一、数据结构
-
在 Thread 类中有一个类型为 ThreadLocal.ThreadLocalMap 的变量 threadLocals,也就是说,每一个线程都有一个自己的 ThreadLocalMap
-
每个线程在往 ThreadLocal 中存放值时,其实就是在往自己的 ThreadLocalMap 中存放值,读取也是从自己的 ThreadLocalMap 中读取值,从而实现了线程隔离
-
ThreadLocalMap 的结构类似于 HashMap,不过 HashMap 是由数组 + 链表 + 红黑树(JDK8) 来实现的,而 ThreadLocalMap 是由数组来实现的
二、内部类解析
-
Entry
static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { // 以当前 ThreadLocal 作为 key super(k); // 以与当前 ThreadLocal 关联的值作为 value value = v; } }
-
Entry 是 ThreadLocalMap 的内部类,其继承自 WeakReference,是一个弱引用
-
当
entry.get()
为 null 时,表示不再引用其关联的 ThreadLocal 对象,此时可以从 ThreadLocalMap 中将该 Entry 对象删除 -
弱引用
-
用于关联非必需对象,被弱引用关联的对象只能生存到下一次垃圾回收之前
-
当 GC 工作时,无论当前内存是否足够,能会回收掉只被弱引用关联的对象
-
-
三、字段解析
-
INITIAL_CAPACITY
private static final int INITIAL_CAPACITY = 16;
- ThreadLocalMap 初始容量,默认为 16,且必须为 2 的幂次方
-
table
private Entry[] table;
- 哈希桶数组,长度必须为 2 的幂次方
-
size
private int size = 0;
- 哈希桶数组容量
-
threshold
private int threshold;
- 扩容阈值
四、构造方法解析
-
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 初始化哈希桶数组 table = new Entry[INITIAL_CAPACITY]; // 计算 firstKey 对应的哈希桶数组下标 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 将 Entry 对象存放至哈希桶数组对应下标处 table[i] = new Entry(firstKey, firstValue); // 设置哈希桶数组容量 size = 1; // 设置扩容阈值 setThreshold(INITIAL_CAPACITY); }
-
使用指定的 key 和 value 来构建 ThreadLocalMap
-
这里需要特别说明的是,明面上我们是以 ThreadLocal 作为 key,以 ThreadLocal 关联的值作为 value 来实例化 Entry 对象,但由于 Entry 是一个弱引用对象,因此实际上我们是以 ThreadLocal 的弱引用对象作为 key,以 ThreadLocal 关联的值作为 value 来实例化 Entry 对象
-
-
ThreadLocalMap(ThreadLocalMap parentMap)
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 对象 Entry e = parentTable[j]; // 如果 Entry 对象不为 null if (e != null) { @SuppressWarnings("unchecked") /* * 获取弱引用关联的 ThreadLocal 对象 * (我们可以简单地将其视为 ThreadLocalMap 的 key,实际上是其弱引用) */ ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); // 如果 key 不为 null if (key != null) { /* * 调用 InheritableThreadLocal 的 * childValue(T parentValue) 方法来获取 value */ Object value = key.childValue(e.value); Entry c = new Entry(key, value); // 计算 key 对应的哈希桶数组下标 int h = key.threadLocalHashCode & (len - 1); // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标 while (table[h] != null) h = nextIndex(h, len); // 将 Entry 对象存放至哈希桶数组新计算出来的下标处 table[h] = c; size++; } } } }
-
将另一个 ThreadLocalMap 中的数据拷贝一份到自身的存储结构中
-
该构造方法仅由
createInheritedMap(ThreadLocalMap parentMap)
方法进行调用
-
五、方法解析
1、setThreshold(int len) 相关方法
-
setThreshold(int len)
private void setThreshold(int len) { threshold = len * 2 / 3; }
- 设置扩容阈值,负载系数为 2/3,即取原容量的 2/3 作为扩容阈值
2、nextIndex(int i, int len) 相关方法
-
nextIndex(int i, int len)
private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
- 以参数 i 作为单位递增,计算下一个哈希桶数组下标
3、prevIndex(int i, int len) 相关方法
-
prevIndex(int i, int len)
private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }
- 以参数 i 作为单位递减,计算上一个哈希桶数组下标
4、getEntry(ThreadLocal<?> key) 相关方法
-
getEntry(ThreadLocal<?> key)
private Entry getEntry(ThreadLocal<?> key) { // 计算 key 对应的哈希桶数组下标 int i = key.threadLocalHashCode & (table.length - 1); // 获取计算得到的下标处的 Entry Entry e = table[i]; /* * 如果 Entry 对象不为 null,且其 get() 方法 * 返回值等于 key,则返回该 Entry 对象 */ if (e != null && e.get() == key) return e; /* * 如果不满足上述条件,则调用 * getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) * 方法来获取 Entry 对象 */ else return getEntryAfterMiss(key, i, e); }
- 根据 key 获取 Entry 对象
-
getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // 如果 Entry 对象不为 null while (e != null) { // 获取弱引用关联的 ThreadLocal 对象 ThreadLocal<?> k = e.get(); /* * 如果获取到的 key 等于参数 key, * 则返回该 Entry 对象 */ if (k == key) return e; /* * 如果获取到的 key 为 null,则调用 * expungeStaleEntry(int staleSlot) * 方法来清除无用数据 */ if (k == null) expungeStaleEntry(i); // 如果获取到的 key 不等于参数 key,则计算下一个下标 else i = nextIndex(i, len); e = tab[i]; } return null; }
- 当根据 key 获取不到 Entry 对象时,使用此方法来获取 Entry 对象
-
expungeStaleEntry(int staleSlot)
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // 清除哈希桶数组指定下标处的数据 tab[staleSlot].value = null; tab[staleSlot] = null; size--; Entry e; int i; // 从 (当前下标加 1) 处开始向后循环 for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { // 获取弱引用关联的 ThreadLocal 对象 ThreadLocal<?> k = e.get(); // 如果获取到的 k 为 null,则清除该数据 if (k == null) { e.value = null; tab[i] = null; size--; // 如果获取到的 k 不为 null } else { // 重新进行哈希运算,计算 k 对应的哈希桶数组的新下标 int h = k.threadLocalHashCode & (len - 1); // 如果新下标不等于旧下标 if (h != i) { // 清除旧下标处的数据 tab[i] = null; // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标 while (tab[h] != null) h = nextIndex(h, len); // 将 Entry 对象存放至哈希桶数组新计算出来的下标处 tab[h] = e; } } } return i; }
-
先清除哈希桶数组指定下标处的数据,然后从 (指定下标加 1) 处开始向后循环
-
循环过程中,清除过期数据;对于不过期的数据,则重新进行哈希运算,定位其在新数组中的位置,如果该位置上已经有数据存在,则将该数据存放至离此位置最近的
Entry = null
的桶中
-
5、set(ThreadLocal<?> key, Object value) 相关方法
-
set(ThreadLocal<?> key, Object value)
private void set(ThreadLocal<?> key, Object value) { 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)]) { // 获取弱引用关联的 ThreadLocal 对象 ThreadLocal<?> k = e.get(); /* * 如果获取到的 k 等于参数 key, * 则对 value 进行赋值,并直接返回 */ if (k == key) { e.value = value; return; } /* * 如果获取到的 k 为 null,则调用 * replaceStaleEntry(ThreadLocal<?> key, * Object value, int staleSlot) 方法来替换过期数据 * 并直接返回 */ if (k == null) { replaceStaleEntry(key, value, i); return; } } /* * 用 key 和 value 重新构建一个 Entry 对象, * 将其存放至哈希桶数组对应下标处 */ tab[i] = new Entry(key, value); int sz = ++size; /* * 调用 cleanSomeSlots(int i, int n) 方法, * 进行启发式清理,如果未清理任何数据,且 * sz 大于等于扩容阈值,则调用 rehash() 方法, * 重新整理或扩大哈希桶数组 */ if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
- 设置与 key 关联的 value
-
replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot)
private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; /* * slotToExpunge 表示进行过期数据 * 清理的起始下标,初始值为 staleSlot */ int slotToExpunge = staleSlot; // 从 (当前下标减 1) 处开始向前循环 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) // 如果弱引用关联的 ThreadLocal 对象为 null if (e.get() == null) // 则将 slotToExpunge 更新为当前下标 i slotToExpunge = i; // 从 (当前下标加 1) 处开始向后循环 for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { // 获取弱引用关联的 ThreadLocal 对象 ThreadLocal<?> k = e.get(); /* * 如果获取到的 k 等于参数 key */ if (k == key) { // 对 value 进行赋值 e.value = value; /* * 交换当前 staleSlot 位置, * 用于维护哈希桶数组顺序 */ tab[i] = tab[staleSlot]; tab[staleSlot] = e; /* * 如果 slotToExpunge 等于 staleSlot * (说明之前的向前循环并没有找到过期数据), * 则将当前下标 i 赋值给 slotToExpunge, * 并从此下标开始向后循环清除过期数据 */ if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } /* * 如果获取到的 k 为 null, * 且 slotToExpunge 等于 staleSlot * (说明之前的向前循环并没有找到过期数据), * 则将当前下标 i 赋值给 slotToExpunge */ if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } /* * 如果找不到 key 对应的 value, * 则用 key 和 value 重新构建一个 Entry 对象, * 将其存放至哈希桶数组对应下标处 */ tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); /* * 如果 slotToExpunge 不等于 staleSlot * (说明之前的向前循环找到了过期数据), * 则从 slotToExpunge 开始向后循环清除过期数据 */ if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
- 将过期数据替换成指定 key、value 的新数据
-
cleanSomeSlots(int i, int n)
private boolean cleanSomeSlots(int i, int n) { // 标记是否清理过数据 boolean removed = false; Entry[] tab = table; int len = tab.length; do { // 计算下一个下标 i = nextIndex(i, len); // 获取计算得到的下标处的 Entry Entry e = tab[i]; /* * 如果 Entry 对象不为 null, * 且其关联的 ThreadLocal 对象为 null * (说明该 Entry 对象已过期) */ if (e != null && e.get() == null) { n = len; removed = true; // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除 i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; }
-
启发式清理,对哈希桶数组长度进行减半循环,在循环中调用
expungeStaleEntry(int staleSlot)
方法对过期数据进行清除 -
如果清理过数据,则返回 true;如果没有清理过数据,则返回 false
-
-
rehash()
private void rehash() { // 清除所有过期数据 expungeStaleEntries(); /* * 如果 size 大于等于 threshold - threshold / 4, * (即 size 大于等 threshold * 3/4) 则进行扩容 */ if (size >= threshold - threshold / 4) resize(); }
- 重新整理或扩大哈希桶数组,首先清理所有过期数据,再对当前哈希桶数组容量进行判断,如果大于等于当前扩容阈值的 3/4,则进行扩容
-
expungeStaleEntries()
private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; // 从头开始遍历哈希桶数组 for (int j = 0; j < len; j++) { Entry e = tab[j]; /* * 如果 Entry 对象不为 null, * 且其关联的 ThreadLocal 对象为 null * (说明该 Entry 对象已过期) */ if (e != null && e.get() == null) // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除 expungeStaleEntry(j); } }
- 清除哈希桶数组中所有过期数据
-
resize()
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; // 新哈希桶数组长度为原哈希桶数组的 2 倍 int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; // 从头开始遍历原哈希桶数组 for (int j = 0; j < oldLen; ++j) { // 获取 Entry 对象 Entry e = oldTab[j]; // 如果 Entry 对象不为 null if (e != null) { // 获取弱引用关联的 ThreadLocal 对象 ThreadLocal<?> k = e.get(); // 如果获取到的 k 为 null if (k == null) { // 将 Entry 对象的 value 设置为 null,方便 GC 对其进行回收 e.value = null; // Help the GC // 如果获取到的 k 不为 null } else { // 计算 k 对应的哈希桶数组下标 int h = k.threadLocalHashCode & (newLen - 1); // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标 while (newTab[h] != null) h = nextIndex(h, newLen); // 将 Entry 对象存放至哈希桶数组新计算出来的下标处 newTab[h] = e; count++; } } } // 设置扩容阈值 setThreshold(newLen); size = count; table = newTab; }
- 对哈希桶数组进行 2 倍扩容
6、remove(ThreadLocal<?> key) 相关方法
-
remove(ThreadLocal<?> key)
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)]) { /* * 如果关联的 ThreadLocal 对象 * 等于参数 ThreadLocal 对象 */ if (e.get() == key) { e.clear(); // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除 expungeStaleEntry(i); return; } } }
- 删除 key 对应 Entry 对象