通过上面的分析。我们能够认识到ThreadLocal事实上是与线程绑定的一个变量,如此就会出现一个问题:假设没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。
因此,ThreadLocal的一个非常大的“坑”就是当使用不当时,导致使用者不知道它的作用域范围。
大家可能觉得线程结束后ThreadLocal应该就回收了。假设线程真的注销了确实是这种,可是事实有可能并不是如此。比如在线程池中对线程管理都是採用线程复用的方法(Web容器通常也会採用线程池)。在线程池中线程非常难结束甚至于永远不会结束。这将意味着线程持续的时间将不可预測,甚至与JVM的生命周期一致。
那么对应的ThreadLocal变量的生命周期也将不可预測。
或许系统中定义少量几个ThreadLocal变量也无所谓。由于每次set数据时是用ThreadLocal本身作为Key的,同样的Key肯定会替换原来的数据。原来的数据就能够被释放了,理论上不会导致什么问题。但世事无绝对,假设ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会開始膨胀。
抛开代码本身的问题。举一个极端的样例。假设不想定义太多的ThreadLocal变量,就用一个HashMap来存放,这貌似没什么问题。由于ThreadLocal在程序的不论什么一个地方都能够用得到,在某些设计不当的代码中非常难知道这个HashMap写入的源头,在代码中为了保险起见。一般会先检查这个HashMap是否存在,若不存在,则创建一个HashMap写进去。若存在,通常也不会替换掉。由于代码编写者一般会“害怕”由于这样的替换会丢掉一些来自“其它地方写入HashMap的数据”。从而导致很多不可预见的问题。
在这种情况下。HashMap第一次放入ThreadLocal中或许就一直不会被释放,而这个HashMap中可能開始存放很多Key-Value信息,假设业务上存放的Key值在不断变化(比如,将业务的ID作为Key),那么这个HashMap就開始不断变长,并且非常可能在每一个线程中都有一个这种HashMap,逐渐地形成了间接的内存泄漏。以前有非常多人吃过这个亏,并且吃亏的时候发现这种代码可能不是在自己的业务系统中。而是出如今某些二方包、三方包中(开源并不保证没有问题)。
要处理这样的问题非常复杂,只是首先要保证自己编写的代码是没问题的。要保证没问题不是说我们不去用ThreadLocal。甚至不去学习它。由于它肯定有其应用价值。在使用时要明确ThreadLocal最难以捉摸的是“不知道哪里是源头”(一般是代码设计不当导致的),仅仅有知道了源头才干控制结束的部分。或者说我们从设计的角度要让ThreadLocal的set、remove有始有终,通常在外部调用的代码中使用finally来remove数据,仅仅要我们细致思考和抽象是能够达到这个目的的。有些是二方包、三方包的问题,对于这些问题我们须要学会的是找到问题的根源后解决,关于二方包、三方包的执行跟踪,可參看第3.7.9节介绍的BTrace工具。
补充:在不论什么异步程序中(包含异步I/O、非堵塞I/O),ThreadLocal的參数传递是不靠谱的,由于线程将请求发送后。就不再等待远程返回结果继续向下运行了,真正的返回结果得到后,处理的线程可能是还有一个。
#####################################个人总结 ####################################
Thread.java源代码中:
ThreadLocal.ThreadLocalMap threadLocals = null;即:每一个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(例如以下所看到的),所以Thread类能够进行引用.
static class ThreadLocalMap {所以每一个线程都会有一个ThreadLocal.ThreadLocalMap对象的引用
当在ThreadLocal中进行设值的时候:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
首先获取当前线程的引用,然后获取当前线程的ThreadLocal.ThreadLocalMap对象(t.threadLocals变量就是ThreadLocal.ThreadLocalMap的变量),假设该对象为空就创建一个,例如以下所看到的:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}这个this变量就是ThreadLocal的引用,对于同一个ThreadLocal对象每一个线程都是同样的,可是每一个线程各自有一个ThreadLocal.ThreadLocalMap对象保存着各自ThreadLocal引用为key的值,所以互不影响,并且:假设你新建一个ThreadLocal的对象,这个对象还是保存在每一个线程同一个ThreadLocal.ThreadLocalMap对象之中,由于一个线程仅仅有一个ThreadLocal.ThreadLocalMap对象,这个对象是在第一个ThreadLocal第一次设值的时候进行创建,如上所述的createMap方法.
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
总结:
API说明:
ThreadLocal(),T get(),protected T initialValue(),void remove(),void set(T value)
典型实例:
1.Hiberante的Session 工具类HibernateUtil
2.通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
ThreadLocal使用的一般步骤:
1、在多线程的类(如ThreadDemo类)中。创建一个ThreadLocal对象threadXxx,用来保存线程间须要隔离处理的对象xxx。
2、在ThreadDemo类中。创建一个获取要隔离訪问的数据的方法getXxx(),在方法中推断,若ThreadLocal对象为null时候,应该new()一个隔离訪问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中。通过getXxx()方法获取要操作的数据。这样能够保证每一个线程相应一个数据对象,在不论什么时刻都操作的是这个对象。
与Synchonized的对照:
ThreadLocal和Synchonized都用于解决多线程并发訪问。可是ThreadLocal与synchronized有本质的差别。synchronized是利用锁的机制,使变量或代码块在某一时该仅仅能被一个线程訪问。而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时可以获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
一句话理解ThreadLocal:向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就仅仅属于这个线程了。