本文将主要结合源码讲述 ThreadLocal 的使用场景和内部结构,以及 ThreadLocalMap 的内部结构;另外在阅读文本以前只好先了解一下引用和 HashMap 的相关知识,能够参考 Reference 框架概览、Reference 彻底解读、HashMap 相关;html
一般状况下避免多线程问题有三种方法:java
而 ThreadLocal 则是经过每一个线程独享状态变量的方式,即不使用共享状态变量,来消除多线程问题的,例如:数组
@Slf4j public class TestThreadlocal { private static ThreadLocal<String> local = ThreadLocal.withInitial(() -> "init"); public static void main(String[] args) throws InterruptedException { Runnable r = new TT(); new Thread(r, "thread1").start(); Thread.sleep(2000); new Thread(r, "thread2").start(); log.info("exit"); } private static class TT implements Runnable { @Override public void run() { log.info(local.get()); local.set(Thread.currentThread().getName()); log.info("set local name and get: {}", local.get()); } } }
// 打印:数据结构
[14 19:27:39,818 INFO ] [thread1] TestThreadlocal - init [14 19:27:39,819 INFO ] [thread1] TestThreadlocal - set local name and get: thread1 [14 19:27:41,818 INFO ] [main] TestThreadlocal - exit [14 19:27:41,819 INFO ] [thread2] TestThreadlocal - init [14 19:27:41,819 INFO ] [thread2] TestThreadlocal - set local name and get: thread2
能够看到线程1和线程2虽然使用的是同一个 ThreadLocal
变量,可是他们之间却没有互相影响;其缘由就是每一个使用 ThreadLocal
变量的线程都会在各自的线程中保存一份 独立 的副本,因此各个线程之间没有相互影响;多线程
ThreadLocal 的大致结构如图所示:框架
如图所示:ide
其源代码以下:this
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocalMap.Entry:线程
另外还须要注意这里的 Entry,code
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; } } ... } Reference(T referent) { this(referent, null); }
能够看到 Entry
继承了 WeakReference
,而且没有传入 ReferenceQueue
;关于 Reference 的部分下面我简单介绍,具体的能够参考我上面提到了两个博客;
WeakReference 表示当传入的 referent(这里就是 ThreadLocal 自身),变成弱引用的时候(即没有强引用指向他的时候);下一次 GC 将自动回收弱引用;这里没有传入 ReferenceQueue,也就表明不能集中监测回收已弃用的 Entry,而须要再次访问到对应的位置时才能检测到,具体内容下面还有讲到,注意这也是和 WeakHashMap 最大的两个区别之一;
注意若是没有手动移除 ThreadLocal,而他有一直以强引用状态存活,就会致使 value 没法回收,至最终 OOM;因此在使用 ThreadLocal 的时候,最后必定要手动移除;
ThreadLocalMap 看名字大体能够知道是相似于 HashMap的数据结构;可是有一个重要的区别是,HashMap 使用拉链法解决哈希冲突,而 ThreadLocalMap 是使用线性探测法解决哈希冲突;具体结构如图所示:
如图所示,ThreadLocalMap 里面没有链表的结构,当使用 threadLocalHashCode & (len - 1);
定位到哈希槽时,若是该位置为空则直接插入,若是不为空则检查下一个位置,直到遇到空的哈希槽;
另外它和咱们一般见到的线性探测有点区别,在插入或删除的时候,会有哈希槽的移动;
源码以下:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); // 延迟初始化 } private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 定位哈希槽 // 若是本来的位置不为空,则依次向后查找 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); // 若是 threadLocal 已经存在,则直接用新值替代旧值 if (k == key) { e.value = value; return; } // 若是向后找到一个已经弃用的哈希槽,则将其替换 if (k == null) { replaceStaleEntry(key, value, i); return; } } // 若是定位的哈希槽为空,则直接插入新值 tab[i] = new Entry(key, value); int sz = ++size; // 最后扫描其余弃用的哈希槽,若是最终超过阈值则扩容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } } private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; int slotToExpunge = staleSlot; // 以 staleSlot 为基础,向前查找到最前面一个弃用的哈希槽,并确立清除开始位置 for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // 以 staleSlot 为基础,向后查找已经存在的 ThreadLocal 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; // 刚交换的位置若是等于清除开始位置,则将其指向目标位置以后 if (slotToExpunge == staleSlot) slotToExpunge = i; // 从开始清除位置开始扫描全表,并清除 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // 若是在目标位置后面未找到目标 ThreadLocal,则 staleSlot 仍然是目标位置,并将开始清除位置指向后面 if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // 在目标位置替换 tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // 若是开始清除的位置,不是目标位置,则扫描全表并清除 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
其中整体思路是:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 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 return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
从源码里面也能够看到上面讲的逻辑:
Thread.withInitial(Supplier<? extends S> supplier)
;工厂方法建立以初始值的 ThreadLocal,或则直接覆盖 Thread.initialValue()
方法;Thread.initialValue()
方法返回初始值;public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } } public void clear() { this.referent = null; }
移除的逻辑也可 HashMap 相似:
int index = key.threadLocalHashCode & (len-1); private final int threadLocalHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647;
这里哈希槽的定位仍然是使用的除留余数法,当容量是2的幂时,hash % length = hash & (length-1)
;可是 ThreadLocalMap 和 HashMap 有点区别的是,ThreadLocalMap 的 key 都是 ThreadLocal,若是这里使用一般意义的哈希计算方法,那确定每一个 key 都会发生哈希碰撞;因此须要用一种方法将相同的 key 区分开,并均匀的分布到 2的幂的数组中;
因此就看到了上面的计算方法,ThreadLocal 的哈希值每次增长 0x61c88647
;具体缘由你们能够参见源码注释,其目的就是能使 key 均匀的分布到 2的幂的数组中;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0); return removed; } private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { 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. while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }
expungeStaleEntry:
cleanSomeSlots 则是向后偏移调用 expungeStaleEntry 方法 log(n)
次,cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
连用就能够扫描全表清除已弃用的哈希槽;
private void rehash() { expungeStaleEntries(); // Use lower threshold for doubling to avoid hysteresis if (size >= threshold - threshold / 4) resize(); } private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; if (e != null && e.get() == null) expungeStaleEntry(j); } } private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 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(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }
扩容时:
InheritableThreadLocal
是能够被继承的 ThreaLocal;在 Thread 中有成员变量用来继承父类的 ThreadLocalMap ;ThreadLocal.ThreadLocalMap inheritableThreadLocals
;好比:
@Slf4j public class TestThreadlocal { private static InheritableThreadLocal<String> local = new InheritableThreadLocal(); public static void main(String[] args) throws InterruptedException { Runnable r = new TT(); local.set("parent"); log.info("get: {}", local.get()); Thread.sleep(1000); new Thread(r, "child").start(); log.info("exit"); } private static class TT implements Runnable { @Override public void run() { log.info(local.get()); local.set(Thread.currentThread().getName()); log.info("set local name and get: {}", local.get()); } } }
// 打印:
[15 10:58:29,878 INFO ] [main] TestThreadlocal - get: parent [15 10:58:30,878 INFO ] [main] TestThreadlocal - exit [15 10:58:30,878 INFO ] [child] TestThreadlocal - parent [15 10:58:30,878 INFO ] [child] TestThreadlocal - set local name and get: child