ThreadLocal的set方法和get方法,从set方法开始:
public void set(T value) {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取线程的局部变量
if (map != null)//判断map是否存在
map.set(this, value);//set值 key是当前ThreadLocal对象 value是value
else
createMap(t, value);//不然 建立一个map设置值
}
get方法:
public T get() {
Thread t = Thread.currentThread();//获取当前线程
ThreadLocalMap map = getMap(t);//获取线程的局部变量map
if (map != null) {//当map存在时
ThreadLocalMap.Entry e = map.getEntry(this);//获取entry(键值对)
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;//返回值
}
}
return setInitialValue();//返回null
}
在了解了ThreadLocal的内部实现后,我看到一个问题,那就是这些变量是维护在Thread类内部的,这也意味着只要线程不退出,对象的引用将一直存在,
当线程退出是,Thread类会进行一些清理工做,其中就包括清理ThreadLocalMap.下面是具体实现:在Thread类内部:
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
所以,若是咱们使用线程池,那就意味着当前线程未必会退出,(好比固定大小的线程池,线程老是存在的,) 若是这样,将一些大大的对象设置到ThreadLocal中,可能会使系统出现内存在泄漏,
此时,你但愿及时的GC,最好使用ThreadLocal.remove()方法将这个变量移除,就像咱们习惯性的关闭数据库连接同样,若是你肯定不须要这个对象了,那么就应该告诉虚拟机,把他回收掉,防止内存泄漏,
另一种有趣的状况是JDK也可能容许你想释放普通变量同样释放ThreadLocal.好比,我么你有时候为了加入GC.会特地写出相似obj=null之类的代码.若是这么作,obj所指向的对象就会更容易地垃圾回收器发现,从而加速回收,
同理,若是对于ThreadLocal的变量,咱们也手动将其设置null.好比t1=null.那么这个ThreadLocal对应的全部线程的局部变量都有可能被回收,咱们写个小例子来看一看奥秘:
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected void finalize() throws Throwable { //重载了finalize() 当对象在GC时,打印信息
System.out.println(this.toString() + " is gc");
}
};
static volatile CountDownLatch cd = new CountDownLatch(10000);//倒计时
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (t1.get() == null) {
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
@Override
protected void finalize() throws Throwable {
System.out.println(this.toString() + " is gc");
}
});
System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
}
Date t = t1.get().parse("2016-12-19 19:29:" + i % 60);
} catch (ParseException e) {
e.printStackTrace();
} finally {
cd.countDown();//完成 计数器减1
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();//等待全部线程 完成准备
System.out.println("mission complete!!");
t1 = null;
System.gc();
System.out.println("first GC complete!!");
t1 = new ThreadLocal<>();
cd = new CountDownLatch(1000);
for (int i = 0; i < 10000; i++) {
es.execute(new ParseDate(i));
}
cd.await();
Thread.sleep(1000);
System.gc();
System.out.println("second GC complete!!");
}
}
输出结果以下:
在主函数Main中,前后进行了2次任务提交,每次10000个任务,在第一次任务提交后,咱们t1设置为null 接着进行了一次gc,接着咱们进行了第二次任务提交,完成后在进行一次gc,
注意这些输出,.咱们发现了当t1被设置为null时候,第一次gc 回收了.接着提交第二次任务,此次咱们也是建立了10个线程,能够看到,虽然咱们手动remove()这些对象,可是系统依然有可能回收他们.
要了解这里的回收机制,咱们须要进一步了解Thread.ThreadLocalMap的实现,以前咱们说过,ThreadLocalMap是一个相似HashMap的东西,更精确地说,他更加相似WeakHashMap.
ThreadLocalMap的实现使用了弱引用,弱引用是比强引用弱的多的引用,java在虚拟机回收时,若是发现若引用,就会当即回收,ThreadLocalMap内部由一系列Entry构成,每个entry都是WeakReferenc<ThreadLocal>:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这里的参数k就是map的key,v就是Map的value.其中k也就是ThreadLocal实例,做为弱引用使用(super(k)就是调用了WeakReferenc的构造函数,)所以,索然这里使用了ThreadLocal做为map的key,可是实际上,他并非真的持有ThreadLocal的引用,而当THreadLocal的外部引用被回收时,ThreadLocalMap中的key就会变成null.当系统进行ThreadLocalMap清理时,就会天然将这些垃圾数据回收,