ThreadPool对ThreadLocal的影响
ThreadLocal
ThreadLocal很容易望文生义”本地线程”。其实ThreadLocal并不是一个本地线程,而是Thread的一个局部变量。ThreadLocal为每一个使用改变量的线程提供一 个独立的副本,每一个线程都可以独立改变自己的副本,而不会影响其他线程所对应的副本。
ThreadLocal实现原理
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
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();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
-
ThreadLocal真正的实现在于ThreadLocal的内部有一个ThreadLocalMap的成员变量
-
ThreadLocalMap的实现跟HashMap的实现差不多,不同的地方在于Hashcode的计算规则,key为Thread本身
-
ThreadLocal的get()方法,可以看出直接在ThreadMap中取map.getEntry(this);
-
ThreadLocal的set()方法,set设置的value是引用,在单线程中,Map中put的对象是引用关系,取出来之后再赋值,再取出来对象也会发生变化 例如下面代码:
Map<String,StringBuffer> map = new HashMap<String,StringBuffer>(); StringBuffer sb = new StringBuffer("aa"); map.put("1",sb); map.put("2",sb); map.get("1").append("3"); System.out.println(map.get("2")); //输出aa3
在看ThreadLocal的源码中,看到value的赋值是引用赋值,并不是拷贝操作。那么很容易产生疑问?: 为什么在调用ThreadLocal变量的set方法,不会对其他线程造成影响。 还是太天真,其实很简单,再多线程环境下,每个线程都有一个栈空间,set(value)的时候,每个线程的value,本来就是不同的。
ThreadLocal的应用举例
SimpleDataFormat是一个线程不安全的类,多线程的情况下一般使用的方式是:
- 每次调用都new出来一个SimpleDataFormat对象
- 使用加锁的方式使用SimpleDataFormat
骚年还是太单纯,ThreadLocal在这里可以用一下
public class Foo{
private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue()
{
return new SimpleDateFormat("yyyyMMdd HHmm");
}
};
public String formatIt(Date date)
{
return formatter.get().format(date);
}
}
网上看到的,不知优雅不优雅,(恩,“优雅”)
ThreadLocal 与在线程池中出现的坑
在代码过程在出现一种错误,一个Service单例中一个ThreadLocal,日志中发现,在没有给这个变量赋值的情况下,取出的值竟然不是空!
原因是:tomcat线程池并不会对使用完的线程回收清理,如果并发量大,取出了已经被使用的线程后,取出的ThreadLocal的变量可能是旧值
解决办法: 在使用ThreadLocal变量的时候,在使用完毕之后,需要显示的主动的释放资源,这是一个很好的习惯
threadLocal.remove();//(听说在某个版本后自动调用这个了,不确定。待定下!)
这样做优点:
-
避免因为线程池的重用而导致的threadlocal变量中get的数据是老线程的数据,而导致不正确
-
在一些Web容器中,比如tomcat、jboss的中,如果不主动释放资源,那么可能会导致out of memory的异常出现。
那么为什么会出现OOM呢?
因为在tomcat或者其他web容器中,通常有线程池。线程池中的线程是复用的,Thread不会被销毁,而ThreadLocal变量是保存在Thread中的ThreadLocalMap中的, 所以如果不主动清理这些变量,那么这些ThreadLocal变量一直会存在线程池中,不被清理。就算tomcat中的应用销毁了,这些threadlocal变量还在。这就造成了memory leak, 但还不至于出现OOM。那么什么情况下出现OOM呢?
当应用不停的reload的情况下会出现OOM。为什么呢?当tomcat中的应用卸载的时候,classloader会被回收,那么GC会清理在perm heap中不被使用的class(注:PermGen中可以被回收的条件是: classloader可以被回收,其下的所有加载过的没有对应实例的类信息(保存在permgen)可以被回收)。发现它无法清理ThreadLocal变量中引用的那个class,因为这个class正在被线程池中的线程使用。 如果那个class又引用了其他class,那么所有被引用的class将都不能被清理。这样就导致这些类信息在PermGen中发生memory leak了。 然后当应用重新load之后,相应的类信息又被新的classloader重新在permGen中加载一遍。在最坏的情况下,应用不停的reload,那么permGen终将被class信息撑爆,造成OOM。