ThreadLocal的实现原理
ThreadLocal 如何保证这些对象只被当前线程访问呢?下面让我们一起深入 ThreadLocal
的内部实现。
我们需要关注的自然是 ThreadLocal的set()方法和get()方法。先从set()方法说起:
public void set (T value) {
Thread t=Thread.currentThread ();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set (this, value);
else
createMap(t, value);
}
在set时,首先获得当前线程对象,然后通过 getMap()方法拿到线程的 ThreadLocalMap,并将值存入ThreadLocalMap 中。而 ThreadLocalMap 可以理解为一个Map(虽然不是,但是你可以把它简单地理解成HashMap),但是它是定义在Thread内部的成员。注意下面的定义是从Thread类中摘出来的:
ThreadLocal.ThreadLocalMap threadLocals = null;
而设置到 ThreadLocal 中的数据,也正是写入了 threadLocals 的这个Map。其中,key为ThreadLocal当前对象,value就是我们需要的值。而threadLocals 本身就保存了当前自己所在线程的所有“局部变量”,也就是一个ThreadLocal变量的集合。
在进行get()方法操作时,自然就是将这个Map中的数据拿出来。
public T get() {
Thread t=Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(thís);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
get()方法先取得当前线程的ThreadLocalMap 对象,然后通过将自己作为key取得内部
的实际数据。
在了解了ThreadLocal的内部实现后,我们自然会引出一个问题:那就是这些变量是维护在Thread类内部的(ThreadLocalMap定义所在类),这也意味着只要线程不退出,对象的引用将一直存在。
当线程退出时,Thread类会进行一些清理工作,其中就包括清理 ThreadLocalMap,注、意下述代码的加粗部分:
/**
*在线程退出前,由系统回调;进行资源清理
*/
private void exit(){
if (group != null) {
group.threadTerminated(this);
group = null;
}
target = null;
/* 加速资源清理 */
threadLocals = nu11;
inheri tableThreadLocals = nu1l;
inheritedAccessControlContext = null;
blocker =null;
uncaughtExceptionHandler = null;
}
因此,使用线程池就意味着当前线程未必会退出(比如固定大小的线程池,线程总是存在)。如果这样,将一些大的对象设置到ThreadLocal中(它实际保存在线程持有的threadLocals Map内),可能会是系统出现内存泄漏的可能(设置了对象到ThreadLocal中,但是不清理它,在使用几次后,这个对象也不再有用了,但是它确无法被回收)。
此时,如果你希望及时回收对象,最好使用ThreadLocal.remove()方法将这个变量移除。就像我们习惯性的关闭数据库连接一样。如果你确实不需要这个对象了,就应该告诉虚拟机把它回收,防止内存泄漏。
另外一种情况是JDK也可能允许你像释放普通变量一样释放ThreadLocal。比如,有时我们为了加速垃圾回收,会特意写出类似obj=null的代码。同理,如果对于ThreadLocal的变量,我们也手动将其设置为null,比如tl=null,那么ThreadLocal对应的所有线程的局部变量都有可能被回收。