概述
从 ThreadLocal 源码学习可知,ThreadLocalMap 是 ThreadLocal 的静态内部类。
而每个 Thread 都有自己的 ThreadLocalMap,Map 中的 key 为 ThreadLocal 变量,value 为 ThreadLocal 的值。不同线程的 ThreadLocalMap 互相不可见,也就保证了线程安全。
public class ThreadLocal<T> {static class ThreadLocalMap {}}
ThreadLocalMap 的成员变量
ThreadLocalMap 用 Entry 数组保存键值对,键为当前的 ThreadLocal 对象,值为 ThreadLocal 对象对应的值
Entry 数组默认的长度为 16,其中阈值 threshold 默认为 INITIAL_CAPACITY *2/3
static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private static final int INITIAL_CAPACITY = 16;private Entry[] table;private int size = 0;private int threshold; // Default to 0private void setThreshold(int len) {threshold = len * 2 / 3;}}
ThreadLocalMap 构造方法
ThreadLocalMap 默认的构造方法,传入的 key 为当前 ThreadLocal 对象,firstValue 为 ThreadLocal 初始设置的值。
hashcode & (len - 1) 其实就是取模运算,定位元素在数组中的位置
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 对长度取模,得到索引位置table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY); // 初始化阈值,超过这个长度需要扩容}private void setThreshold(int len) {threshold = len * 2 / 3; // 默认为数组长度的 2/3}// ThreadLocal 中的方法private final int threadLocalHashCode = nextHashCode();private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}
其次,ThreadLocalMap 的构造方法还支持传入其它的 ThreadLocalMap 进行初始化
大致原理为:新建一个和原来长度一样的 Entry 数组,遍历原数组,排除 key = null 的节点,放到新的 Entry 数组中。
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();if (key != null) { // entry 元素的 key 不为空时Object value = key.childValue(e.value); // 其实是返回 ThreadLocal 对应的值Entry c = new Entry(key, value); // 新建 Entry 节点int h = key.threadLocalHashCode & (len - 1); // 取模while (table[h] != null) // 数组已有元素,产生冲突h = nextIndex(h, len);table[h] = c;size++;}}}}// 返回当前 i 的后一位,若 i 已经是最后一位,返回第 0 位,依次循环private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}
成员方法
getEntry
根据 ThreadLocal 获取 entry 节点,若 entry 为 null,那么直接返回 null;
若 entry 中保存的 threadlocal 变量和传进来的 threadlocal 变量不一致,那么向后遍历下一个节点,直到找到相同的节点;
若没有相同节点,找到 key = null 的节点那么返回 null。
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1); // 对长度取模定位元素在数组的位置Entry e = table[i];if (e != null && e.get() == key)return e; // 节点是同一个,直接返回else// 没找到节点返回 null// 数组中的 entry 和 threadLocal 不一致return getEntryAfterMiss(key, i, e);}// 顺序向后遍历,直到找到满足的点private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) { // 节点为 null 直接返回 nullThreadLocal<?> k = e.get(); // k 为 entry 数组中的 entry 对应的 keyif (k == key)return e;if (k == null) // 若 key 被置为空expungeStaleEntry(i); // 清除 key 为 null 的节点elsei = nextIndex(i, len); // 找到下一个节点e = tab[i];}return null;}
expungeStaleEntry
staleSlot 表示过时的插槽,方法的意思在下面有注释,主要包含:
- 清除 key = null 的 entry,便于垃圾回收
2. 从 staleSlot 位置开始遍历 entry 数组,对数组中元素进行 rehash 操作,若遍历到的数组的 key = null,结束遍历。
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;tab[staleSlot].value = null; // 将 staleSlot 处的 key,value 都置为 null,方便垃圾回收tab[staleSlot] = null;size--;Entry e;int i;// 开始进行 rehash 操作,即遍历数组直至遇到 key = nullfor (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {// 遍历 staleSlot 后面的节点ThreadLocal<?> k = e.get();if (k == null) { // 删除 key = null 的节点e.value = null;tab[i] = null;size--;} else {// 重新计算该 key 对应数组中的位置int h = k.threadLocalHashCode & (len - 1);if (h != i) { // 节点不一致,表示需要迁移tab[i] = null; // 原来的位置置为 nullwhile (tab[h] != null) // 找到第一个为 null 的位置,插入元素h = nextIndex(h, len);tab[h] = e;}}}return i;}
set
将键值对 set 到 entry 数组中
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)]) {// 获取数组中的 entry 的 ThreadLocal 引用ThreadLocal<?> k = e.get();// 如果 key 一样,覆盖 valueif (k == key) {e.value = value;return;}// 传进来的 key 为 null,将 i 处的节点设为 key-value,并清除后面 key = null 的节点if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;// 超过阈值,进行 rehashif (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}
replaceStaleEntry
private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;// 当前需要清除的节点位置int slotToExpunge = staleSlot;// 从当前节点往前遍历,找到 value 为 null 的位置for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;// 从 staleSlot 位置开始往后遍历for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get(); // 获取 threadlocal 的引用if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;// Start expunge at preceding stale entry if it existsif (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}// If we didn't find stale entry on backward scan, the// first stale entry seen while scanning for key is the// first still present in the run.if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}// If key not found, put new entry in stale slottab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// If there are any other stale entries in run, expunge themif (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}
ThreadLocal继承性解决方案https://blog.csdn.net/weixin_42200859/article/details/105396338
