使用场景-用途
每个线程需要一个独享的对象
(通常是工具类,典型需要使用的类有SimpleDataFormat和Random)
ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("HH:mm:ss");
}
};
每个线程内需要保存全局变量
(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦
2种用法
initialValue:重写赋值
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);
}
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();
}
注意点
内存泄漏
- ThreadLocal的每个Entry都是一个对key的弱引用,同时,每个Entry都包含了一个对Value的强引用
- 正常情况下,当线程终止时,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了
- 但是线程不终止(比如使用线程池时),那么key对应的value就不会被回收,就可能出现OOM
- JDK已经考虑到这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value设为null,这样value对象就可以被回收
- 但是一个ThreadLocal不被使用时,实际上不会调用set,remove,rehash方法,那么value就不会被回收
如何避免
调用remove方法,就会删除对应的Entry对象,就可以避免内存泄漏,所以在使用完ThreadLocal对象后,应该调用remove方法
空指针异常
当ThreadLocal里没有存储value时,会返回Null,但需要注意一些场景,(比如Long对象自动装箱拆箱转成基本类型long时,出现的空指针异常)
共享对象
如果每个线程中ThreadLocal.set()进去的对象本来就是多线程共享的对象(比如static对象),还是会出现线程安全问题