threadlocal 是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
ThreadLocal
//set 方法public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //实际存储的数据结构类型 ThreadLocalMap map = getMap(t); //如果存在map就直接set,没有则创建map并set if (map != null) map.set(this, value); else createMap(t, value); } //getMap方法ThreadLocalMap getMap(Thread t) { //thred中维护了一个ThreadLocalMap return t.threadLocals; } //createMapvoid createMap(Thread t, T firstValue) { //实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals t.threadLocals = new ThreadLocalMap(this, firstValue);}
复制代码
通过 set 方法可以看到 当前线程保存了一个 ThreadLocalMap 类型的 threadLocals,已 threadLocal 为 key 存放了当前数据 value.
//ThreadLocal中get方法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中getEntry方法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); }
复制代码
ThreadLocalMap
set 方法
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用//同时让ThreadLocal和储值形成key-value的关系static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value;
Entry(ThreadLocal<?> k, Object v) { super(k); value = v; }}
//ThreadLocalMap构造方法ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { //内部成员数组,INITIAL_CAPACITY值为16的常量 table = new Entry[INITIAL_CAPACITY]; //位运算,结果与取模相同,计算出需要存放的位置 //threadLocalHashCode比较有趣 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY);}
复制代码
每个线程 Thread 持有一个 ThreadLocalMap 类型的实例 threadLocals,结合此处的构造方法可以理解成每个线程 Thread 都持有一个 Entry 型的数组 table,而一切的读取过程都是通过操作这个数组 table 完成的。
Entry 是一个弱引用,threadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 不存在外部强引用时,Key(ThreadLocal)势必会被 GC 回收,这样就会导致 ThreadLocalMap 中 key 为 null, 而 value 还存在着强引用,只有 thead 线程退出以后,value 的强引用链条才会断掉。比如线程池的存在会导致内存泄露。
当 ThreadLocalMap 的 key 为弱引用回收 ThreadLocal 时,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。当 key 为 null,在下一次 ThreadLocalMap 调用 set(),get(),remove()方法的时候会被清除 value 值。
Remove()方法
private void remove(ThreadLocal<?> key) { //使用hash方式,计算当前ThreadLocal变量所在table数组位置 Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //再次循环判断是否在为ThreadLocal变量所在table数组位置 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //调用WeakReference的clear方法清除对ThreadLocal的弱引用 e.clear(); //清理key为null的元素 expungeStaleEntry(i); return; } }
复制代码
ThreadLocalMap 的 set 方法,当 key 为 null 时清除 value.
if (k == null) { replaceStaleEntry(key, value, i); return;}
复制代码
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not.
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();
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();}
复制代码
避免内存泄露的方法:每次使用完 ThreadLocal 都调用它的 remove()方法清除数据。
ThreadLocal 的使用场景
最常见的场景是在拦截器 interceptor 解析用户信息,并将用户信息放入到 ThreadLocal 中。
public class UserContext { private static ThreadLocal<User> localUser = ThreadLocal.withInitial(() -> { return new User(); });
public static void setUser(User user) { localUser.set(user); }
public static User getUser() { return localUser.get(); }
复制代码
Spring 中的 RequestContextHolder 就是使用了 TreadLocal 存放信息。
基于 Session 的会话管理也用到了 TreadLoca,如 shiro 的 subject 内部用到了 Treadlocal。
评论