因为资源位重构项目中使用了ThreadLocal存放用户权限数据,作为访问接口敲门砖的同时也为接口业务分析提供了用户数据,因此去研究了一下ThreadLocal源码,源码涉及 Thread
、 ThreadLocal
、 InheritableThreadLocal
三个类文件。
定义
ThreadLocal
作为一个泛型类,主要作用是提供线程局部变量,这些变量和普通变量不同之处在于访问它们的每个线程都有属于它们自己的、独立初始化的变量副本,不同线程之间数据独立不共享。
继承了 ThreadLocal
的 InheritableThreadLocal
提供的线程局部变量可在父子线程间进行传递。
ThreadLocal
变量通常被private static
修饰,当一个线程结束时,它所使用的所有 ThreadLocal
相对的实例副本都可被回收,但是对应的value却还存在与内存中,因此可能造成内存泄漏,需要在线程结束之前remove掉数据。
源码
Thread、ThreadLocal、InheritableThreadLocal类别关系
Thread类里面维护了两个ThreadLocalMap对象
threadLocals
存放了只能当前线程获取的局部变量
inheritableThreadLocals
存放可被子线程继承同时继承父线程的局部变量 inheritableThreadLocals
每个ThreadLocalMap对象维护了一个Entry[] table数组,存放了所有的线程局部变量,同时维护了每个ThreadLocal与线程局部变量的映射关系,我们可以通过其中一个ThreadLocal实例获取此Thread实例在此ThreadLocal中的变量值。
线程与ThreadLocal的关系图如下:
源码分析
在Thread类中,维护两个ThreadLocalMap对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Thread implements Runnable { ......
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; ...... }
|
threadLocals
的数据来源有两种:
ThreadLocal.set()
方法的调用;
- 重写
ThreadLocal
的 initialValue()
方法,ThreadLocal.get()
方法调用且 threadLocal
无局部变量时触发
inheritableThreadLocals
的数据来源有三种:
- 子线程创建的时候从父线程中继承线程局部变量
InheritableThreadLocal.set()
方法的调用;
- 重写
inheritableThreadLocal
的 initialValue()
方法,inheritableThreadLocal.get()
方法调用且 inheritableThreadLocal
无局部变量时触发的 initialValue()
方法.
在ThreadLocal类中,我们经常用到的几个方法是get()、set()和remove()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| 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; }
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
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; } } } private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); }
|
在InheritableThreadLocal类中,我们经常用到的几个方法是set()、get()、remove()
Thread在创建线程时的 init()
方法中继承了父线程的 inheritableThreadLocals
数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { ...... if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); }
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len];
for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings(unchecked) ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
protected T childValue(T parentValue) { return parentValue; }
ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }
void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }
|
应用场景
ThreadLocal
的目的是解决多线程场景中出现相同变量访问冲突问题,当某些数据是以线程为作用域并且不同线程具有不同数据的时候,就可以考虑采用 ThreadLocal
。比如用户访问接口时需要用户数据,每个用户的数据都不同,但是局部变量相同,因此就可以使用 ThreadLocal
存储数据
注意场景
ThreadLocal
的错误使用可能会出现以下问题:
防止内存泄漏
ThreadLocalMap
类维护了 ThreadLocal
与具体实例的映射, ThreadLocalMap
的每个 Entry 都是一个对键的弱引用,而Entry却包含了对值的强引用。使用弱引用的原因在于,当没有强引用指向 ThreadLocal
变量时,它可被回收,但是当 ThreadLocal
变量被回收后,该映射的键变为 null,该 Entry无法被移除。从而使得实例被该 Entry引用而无法被回收造成内存泄漏。因此在不用线程局部变量或者线程结束之前,先将数据remove掉。
脏数据
线程池的存在会导致线程复用场景出现,若上一个线程没有在使用完成之后清除 ThreadLocal
信息或下一个线程在使用前没有清理数据、 ThreadLocal
初始化,都可能会拿到上个线程设置的错误数据。