一文搞懂 ThreadLocal 原理( 三 )


/** * 返回当前线程 thread 持有的 ThreadLocalMap * * @param t 当前线程 * @return ThreadLocalMap */ThreadLocalMap getMap(Thread t) { return t.threadLocals;}getMap 方法的作用主要是获取当前线程内的 ThreadLocalMap 对象,原来这个 ThreadLocalMap 是线程的一个属性,下面让我们看看 Thread 中的相关代码:
/** * ThreadLocal 的 ThreadLocalMap 是线程的一个属性,所以在多线程环境下 threadLocals 是线程安全的 */ThreadLocal.ThreadLocalMap threadLocals = null;可以看出每个线程都有 ThreadLocalMap 对象,被命名为 threadLocals,默认为 null,所以每个线程的 ThreadLocals 都是隔离独享的 。
调用 ThreadLocalMap.set() 时,会把当前 threadLocal 对象作为 key,想要保存的对象作为 value,存入 map 。
其中 ThreadLocalMap.set() 的源码如下:
/** * 在 map 中存储键值对<key, value> * * @param keythreadLocal * @param value 要设置的 value 值 */private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; // 计算 key 在数组中的下标 int i = key.threadLocalHashCode & (len - 1); // 遍历一段连续的元素,以查找匹配的 ThreadLocal 对象 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {// 获取该哈希值处的ThreadLocal对象ThreadLocal<?> k = e.get();// 键值ThreadLocal匹配,直接更改map中的valueif (k == key) {e.value = https://www.isolves.com/it/cxkf/bk/2020-07-29/value;return;}// 若 key 是 null,说明 ThreadLocal 被清理了,直接替换掉if (k == null) {replaceStaleEntry(key, value, i);return;} } // 直到遇见了空槽也没找到匹配的ThreadLocal对象,那么在此空槽处安排ThreadLocal对象和缓存的value tab[i] = new Entry(key, value); int sz = ++size; // 如果没有元素被清理,那么就要检查当前元素数量是否超过了容量阙值(数组大小的三分之二),以便决定是否扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) {// 扩容的过程也是对所有的 key 重新哈希的过程rehash(); }}相信到这里,大家应该对 Thread、ThreadLocal 以及 ThreadLocalMap 的关系有了进一步的理解,下图为三者之间的关系:

一文搞懂 ThreadLocal 原理

文章插图
 
ThreadLocal 的 get 方法了解完 set 方法后,让我们看下 get 方法,源码如下:
/** * 返回当前 ThreadLocal 对象关联的值 * * @return */public T get() { // 返回当前 ThreadLocal 所在的线程 Thread t = Thread.currentThread(); // 从线程中拿到 ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) {// 从 map 中拿到 entryThreadLocalMap.Entry e = map.getEntry(this);// 如果不为空,读取当前 ThreadLocal 中保存的值if (e != null) {@SuppressWarnings("unchecked")T result = (T) e.value;return result;} } // 若 map 为空,则对当前线程的 ThreadLocal 进行初始化,最后返回当前的 ThreadLocal 对象关联的初值,即 value return setInitialValue();}get 方法的主要流程为:
  • 先获取到当前线程的引用
  • 获取当前线程内部的 ThreadLocalMap
  • 如果 map 存在,则获取当前 ThreadLocal 对应的 value 值
  • 如果 map 不存在或者找不到 value 值,则调用 setInitialValue() 进行初始化
get 方法的时序图如下所示:
一文搞懂 ThreadLocal 原理

文章插图
 
其中每个 Thread 的 ThreadLocalMap 以 threadLocal 作为 key,保存自己线程的 value 副本,也就是保存在每个线程中,并没有保存在 ThreadLocal 对象中 。
其中 ThreadLocalMap.getEntry() 方法的源码如下:
/** * 返回 key 关联的键值对实体 * * @param key threadLocal * @return */private Entry getEntry(ThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // 若 e 不为空,并且 e 的 ThreadLocal 的内存地址和 key 相同,直接返回 if (e != null && e.get() == key) {return e; } else {// 从 i 开始向后遍历找到键值对实体return getEntryAfterMiss(key, i, e); }}ThreadLocalMap 的 resize 方法当 ThreadLocalMap 中的 ThreadLocal 的个数超过容量阈值时,ThreadLocalMap 就要开始扩容了,我们一起来看下 resize 的源代码:
/** * 扩容,重新计算索引,标记垃圾值,方便 GC 回收 */private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; // 新建一个数组,按照2倍长度扩容 Entry[] newTab = new Entry[newLen]; int count = 0; // 将旧数组的值拷贝到新数组上 for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();// 若有垃圾值,则标记清理该元素的引用,以便GC回收if (k == null) {e.value = https://www.isolves.com/it/cxkf/bk/2020-07-29/null;} else {// 计算 ThreadLocal 在新数组中的位置int h = k.threadLocalHashCode & (newLen - 1);// 如果发生冲突,使用线性探测往后寻找合适的位置while (newTab[h] != null) {h = nextIndex(h, newLen);}newTab[h] = e;count++;}} } // 设置新的扩容阈值,为数组长度的三分之二 setThreshold(newLen); size = count; table = newTab;}


推荐阅读