# ThreadLocal 面试官:了解ThreadLocal嘛?用过ThreadLocal嘛? 我:了解过:它是这样的,假如想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK中提供的`ThreadLocal`类正是为了解决这样的问题。 **`ThreadLocal`类主要解决的就是让每个线程绑定自己的值,可以将`ThreadLocal`类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。**如果你创建了一个`ThreadLocal`变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是`ThreadLocal`变量名的由来。他们可以使用 `get()` 和 `set()` 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。 ## 底层原理 面试官:你知道底层原理嘛? 我:知道 首先看:结构 ```java public class Thread implements Runnable { ...... //与此线程有关的ThreadLocal值。由ThreadLocal类维护 ThreadLocal.ThreadLocalMap threadLocals = null; ...... } // 由源码可见,ThreadLocal存储的变量存在Thread的ThreadLocalMap中 // 可理解为ThreadLocalMap是专门定制的一种Map ``` 其次看set和get方法 ```java // set public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 拿到定制的ThreadLocalMap的map ThreadLocalMap map = getMap(t); if (map != null) //不为空,则绑定 map.set(this, value); else // 否则创建 createMap(t, value); } // getMap 返回的就是threadLocals ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // get public T get() { // 依然获取当前线程 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { // 获取当前Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") // 如果不为空,则返回 T result = (T)e.value; return result; } } return setInitialValue(); } ``` **最终的变量是放在了当前线程的 `ThreadLocalMap` 中,并不是存在 `ThreadLocal` 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。** **每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为key的键值对。** 比如我们在同一个线程中声明了两个 `ThreadLocal` 对象的话,会使用 `Thread`内部都是使用仅有那个`ThreadLocalMap` 存放数据的,`ThreadLocalMap`的 key 就是 `ThreadLocal`对象,value 就是 `ThreadLocal` 对象调用`set`方法设置的值。`ThreadLocal` 是 map结构是为了让每个线程可以关联多个 `ThreadLocal`变量。这也就解释了ThreadLocal声明的变量为什么在每一个线程都有自己的专属本地变量。 ## 内存泄漏 面试官:知道内存泄露嘛? 我:知道,`ThreadLocalMap` 中使用的 key 为 `ThreadLocal` 的弱引用,而 value 是强引用。所以,如果 `ThreadLocal` 没有被外部强引用的情况下,在垃圾回收的时候会 key 会被清理掉,而 value 不会被清理掉。这样一来,`ThreadLocalMap` 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap实现中已经考虑了这种情况,在调用 `set()`、`get()`、`remove()` 方法的时候,会清理掉 key 为 null 的记录。使用完 `ThreadLocal`方法后 最好手动调用`remove()`方法。