ThreadLocal 线程本地变量,顾名思义
(1) 每个Thread 都会创建私有的变量的副本,存在Thread 的 ThreadLocals 变量中,ThreadLocals 是 ThreadLocalMap 类型 ,每个线程可能会有多个ThreadLocal变量。线程私有所以ThreadLocal 线程安全的ThreadLocalMap 中有个 Entry[] table数组用于存储的一个一个的Entry 对象,Entry key - value 键值对 key threadlocal 本身,value 具体的泛型数据
注1:【扩容】ThreadLoalMap默认容量是 16,阈值是容量的 2/3 , 当你的entry 的 大于容量的 3/4 时候触发,ThreadLocalMap的扩容
注2:【什么情况会引起扩容?】在我们set()方法调用的时候,当保存或替换完成后会去启发式的扫描一些【槽】,查找过期的entry, 如果没有这样的过期的数据的化,并且当前的 size >= threshold阈值时候触发rehash() 在哈希,从下一个位置向后扫描直到遇到 null 槽,同时将过期的(key == null)的槽清空,如果size 仍然大于 容量的 3/4 的话就会进行扩容操作
注3: 【怎么扩容?】resize()方法 创建一个当前数组大小的 2 的一个新数组,遍历旧数组同时会清除过期数据(key ==null)或者进行再哈希 (threadLocal的 hashcode 和 新数组的长度 - 1 进行 & 按位与运算,散列到新数组),遍历完后,设置新的容量,table设置为新的 table
注4:【内存泄露】 ThreadLocal 内存泄露主要因为 ThreadLocalMap 中 Entry对象对 threadLocal变量的引用是一个弱引用 ,弱引用仅能存在到 下次GC之前,而 线程对Entry 是一个强引用,当ThreadLocal变量被回收之后 Entry的key==null 变成过期数据,如果没有显示的调用remove() 方法就会造成内存泄露,get() 方法也会对这些过期数据进行清除但不是必然会发生的,如果get() 是当前的ThreadLocal对象则直接返回 e ,否则发生过哈希冲突导致在散列了,此时就会触发如果key==null就会进行清除处理 set() 时也会触发 清除操作
注5:【如何新增数据】set() 操作根据当前线程获取ThreadLocalMap, 获取当前ThreadLocal的值 ,分三种情况: (1) 散列的位上本来就为空则直接新增 Entry(this, value) (2) 当前位置有值且key == 当前的ThreadLocal 直接覆盖 (3) 当key == null时 说明这是过期数据,replaceStaleSlot() 替换,首先向前查找到第一个过期数据的索引下标,然后向后遍历查找,key = 当前threadLocal的 槽位,则与传过来的位置进行交换,并进行 清除过期数据的过程
源码注释:
public T get() { Thread t = Thread.currentThread(); MeThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) { /** * 获取 Entry */ MeThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } private MeThreadLocal.ThreadLocalMap.Entry getEntry(MeThreadLocal<?> key) { int i = key.threadLocalHashCode & (table.length - 1); MeThreadLocal.ThreadLocalMap.Entry e = table[i]; if (e != null && e.get() == key) return e; else /** * 当未查询到 ThreadLocal 时,说明发生了冲突,需要清空 索引 i */ return getEntryAfterMiss(key, i, e); } private MeThreadLocal.ThreadLocalMap.Entry getEntryAfterMiss(MeThreadLocal<?> key, int i, MeThreadLocal.ThreadLocalMap.Entry e) { MeThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; while (e != null) { MeThreadLocal<?> k = e.get(); if (k == key) /** * 当前Entry 是 本ThreadLocal 肯定不会走 */ return e; if (k == null) /** * 清空过期数据,将位置 i 的 在散列到其他位置上, 此时 i 位置就 为 null 了 退出循环 */ expungeStaleEntry(i); else /** * 向后查找,直到找到 null的【槽】 */ i = nextIndex(i, len); e = tab[i]; } return null; } private int expungeStaleEntry(int staleSlot) { MeThreadLocal.ThreadLocalMap.Entry[] tab = table; int len = tab.length; /** * 清空当前位置数据 */ // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null MeThreadLocal.ThreadLocalMap.Entry e; int i; /** * 从 下一个位置开始遍历 为空则清除,否则进行再散列 */ for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { MeThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { /** * 如果下一个不为空且扔存活,则将其进行散列到其他位置上,此时 i 位置就为空,然后返回 i */ int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. /** * 从散列的新位置开始向后找,直到找到一个 null */ while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }