ThreadLocal是什么
ThreadLocal提供了一个线程内的局部变量。这种变量在多线程环境下访问能够保证各个线程里的变量相对独立于其他线程内的变量。
只要线程存活并且ThreadLocal实例可以访问,每个线程都保存对其线程局部变量副本的隐式引用; 线程消失后,线程本地实例的所有副本都将被垃圾收集,除非存在对这些副本的其他引用。
ThreadLocal的实现原理
ThreadLocal的基本操作都是对ThreadLocalMap的一些操作实现,而线程的局部变量就是保存在ThreadLocalMap中的。在Thread类中存在一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,在Thread类中对threadLocals的注释是这样的:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这个变量是通过ThreadLocal进行维护的。
可以看出来,线程的局部变量不是存放在ThreadLocal实例里边的,而是存放在Thread的threadLocals变量里边,只是通过ThreadLocal对线程的局部变量的进行维护。
ThreadLocal的基本操作
void set(T value)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
首先获取当前线程,然后获取当前线程内的ThreaLocalMap:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
只是简单的返回Thread内的threadLocals,第一次调用时,threadLocals为null,通过createMap创建Thread的变量threadLocals:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
如果threadLocals不为null,就把变量放入当前线程的threadLocals
T 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中获取不到值的话,会通过在setInitialValue()方法中调用initialValue()获取初始化值设置给ThreadLocalMap:
protected T initialValue() { return null; }
大概可以明白,在我们创建ThreadLocal的时候,可以通过覆盖initialValue()给线程内的局部变量一个默认的初始化值。
void remove()
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
withInitial()
在JDK8中提供这样一个静态方法public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
通过这个静态方法能够创建一个可以返回初始值的ThreaLocal,这个方法是为了满足JDK8的函数式编程方式,创建ThreadLocal还是挺方便的。
需要注意的是,ThreadLocalMap是使用ThreadLocal的弱引用WeakReference作为key的。为了防止内存泄露,在调用getEntry方法和set方法时,ThreadLocal的直接索引位置获取到Entry e,如果e不为null,而key = e.get()为null或者不一致,就进行一些操作:如果key==null,则直接擦除该位置上的Entry;如果key不一致,查找下一个位置上的Entry e,直到这个e对应的key一致返回e,在这个过程中,key==null的Entry都会被擦除。