转载,原文链接:http://www.cnblogs.com/goofy/archive/2011/11/11/2245907.html
跟大家分享一个问题排查的时候发现的关于ThreadLocal 的问题。 道行尚浅,如失偏颇,欢迎斧正
排查时查ic 的日志,发现记录的调用来源IP 记录错乱。查看IC 相关代码,发现记录IP 用的costomerIP 是一个static 的ThreadLocal 类变量。
之前收藏夹系统接触过ThreadLocal ,印象中是用一个Map 实现的,既然与线程有关,那么web 应用都使用了线程池来对线程进行重用,那么会不会是线程重用引起的呢?仔细看了ThreadLocal 的代码。
原来java 里的Thread 对象里有一个ThreadLocalMap ,key 是ThreadLocal 对象,value 是ThreadLocal 的值,也就是说在对ThreadLocal 赋值时其实是对当前Thread 的一个ThreadLocalMap 进行操作,而ThreadLocal 本身只是一个key 。Get 操作同理。这也解释了为什么一个静态变量在不同的线程中可以有不同的值。
ThreadLocal.set(Object 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);
}
这样就漂亮实现了一个线程独立的value holder ,当然还有很多细节的处理。但是如果使用不小心就会出问题。
比如这里线程池会对线程进行重用,但是重用时并且不会重置其中的ThreadLocalMap ,一个请求过来时,可能并没有新起一个线程,而是重用之前的一个线程,这时如果这个ThreadLocal 没有进行set ,那么直接get 出来的可能就是之前请求时set 的值,就会出现前面提到的ip 记录错乱的问题。所以使用ThreadLocal ,一定要保证get 之前进行过set 。
package tk.mysweetie.java.test;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author wangzhengyang.pt
* 使用TreadPool时的ThreadLocal示例
*/
public class TestThreadPool {
private static ThreadLocal<Boolean> bol = new ThreadLocal<Boolean>();
/**
* 第二个提交的Runnable没有对TreadLocal进行set,但是已经被set过了
*/
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 1, 100, TimeUnit. SECONDS ,
new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.AbortPolicy() );
// 进行set的Runnable
pool.execute(new Runnable() {
@Override
public void run() {
/** *set为true** */
bol.set(true );
System.out.println("" + Thread.currentThread() + "seted bol:" + bol.get());
}
});
// 没有set的Runnable
pool.execute(new Runnable() {
@Override
public void run() {
System.out.println("" + Thread.currentThread() + "not seted bol:" + bol.get());
}
});
}
}