ThreadLocal
是jdk中一个非常重要的工具,它可以控制堆内存中的对象只能被指定线程访问,如果你经常阅读源码,基本在各大框架都能发现它的踪影。而它最经典的应用就是 事务管理
,同时它也是面试中的常客。
我们知道,堆内存是共享的,为什么ThreadLocal能够控制指定线程访问呢? 如图:
get ThreadLocalMap
我们从构建一个ThreadLocal到调用它的set,get方法完整的分析一遍它的源码。
当我们使用 new ThreadLocal<>()
new一个ThreadLocal对象时,它初始化了一个成员变量 threadLocalHashCode
,这个成员变量代表当前ThreadLocal的hashcode值,而它肯定是唯一的:
nextHashCode nextHashCode
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } 复制代码
ThreadLocalMap
map。
public class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } private Entry[] table; private int size = 0; private static final int INITIAL_CAPACITY = 16; private int threshold; // Default to 0 } 复制代码
类似于ArrayList内部的构造,它内部有一个 Entry
数组table,并且Entry继承自弱引用(gc时保存的ThreadLocal会被标记清理),所以每一个Entry中保存着两个值, ThreadLocal
, value
,value既是我们要保存的值。
接着,我们回过头详细分析第三步,ThreadLocalMap的set方法:
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); // 1 for (Entry e = tab[i]; e != null; // 2 e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { // 3 e.value = value; return; } if (k == null) { // 4 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); // 5 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) // 6 rehash(); } 复制代码
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(); } 复制代码
getEntryAfterMiss
rehash,计算数组大小。 从上面的代码分析中,我们知道,ThreadLocalMap的生命周期和当前线程同步,如果当前线程被销毁,则map中的所有引用均被销毁。但如果当前线程不被销毁呢(线程池,tomcat处理请求等)?Entry中保存了ThreadLocal的弱引用以及value,gc时可能清理掉ThreadLocal,而这个value确再没有访问之地,这个时候就会造成内存泄漏! 所以我们需要手动调用remove方法清理掉当前线程ThreadLocalMap的引用!