ThreadLocal
是jdk中一个非常重要的工具,它可以控制堆内存中的对象只能被指定线程访问,如果你经常阅读源码,基本在各大框架都能发现它的踪影。而它最经典的应用就是 事务管理
,同时它也是面试中的常客。
我们知道,堆内存是共享的,为什么ThreadLocal能够控制指定线程访问呢? 如图:
get
方法。 ThreadLocalMap
。 我们从构建一个ThreadLocal到调用它的set,get方法完整的分析一遍它的源码。
当我们使用 new ThreadLocal<>()
new一个ThreadLocal对象时,它初始化了一个成员变量 threadLocalHashCode
,这个成员变量代表当前ThreadLocal的hashcode值,而它肯定是唯一的:
nextHashCode
。 每次新new一个ThreadLocal对象,调用这个生成器同步方法获取hashcode。
因为依赖于静态成员变量 nextHashCode
的关系,所以它的hashcode肯定唯一!
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
获取当前线程t。
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继承自弱引用,所以每一个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的引用!
关注我,这里只有干货!
谢谢你支持我分享知识
扫码打赏,心意已收
打开 微信 扫一扫,即可进行扫码打赏哦