ThreadLocal基础知识
很多地方叫做线程本地变量,也有些地方叫做线程本地存储。
ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。
线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。
Synchronized 用于线程间的数据共享,而 ThreadLocal 则用于线程间的数据隔离。
原理
在Java中,每个线程都拥有一个ThreadLocalMap,在里面存放线程自己的私有对象。ThreadLocalMap里有个Entry对象,继承了WeakReference。Entry对象里有个键值对,其中key是ThreadLocal,value为私有对象。
static class Entry extends WeakReference<ThreadLocal<?>>{
Object value;
Entry(ThreadLocal<?>k,Object v){
super(k);
value = v;
}
}
在ThreadLocal的生命周期中,存在以下引用。实线代表强引用,虚线代表弱引用。
当线程没有结束,但是ThreadLocal对象由于是用的弱引用,GC时被回收了。这时就可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量(value)还存在,且有强引用正在引用,不会被GC)。
解决办法:
(1)使用完线程共享变量后,显式调用ThreadLocalMap.remove()方法清除线程共享变量;
(2)在ThreadLocal的get(),set(),remove()
的时候都会清除线程ThreadLocalMap里所有key为null的value。但如果只创建了ThreadLocal却没有使用这些方法,还是会导致内存泄漏。
(3)ThreadLocal定义为private static,但是这样延长了ThreadLocal的生命周期,可能导致内存泄漏。
总结
1)实际通过ThreadLocal创建的副本是存储在每个线程自己的threadLocalMap中的;
2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,可以用不同的ThreadLocal作为key,区分不同的value方便存取。
3)在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。
应用场景
最常见的ThreadLocal使用场景:用来解决数据库连接、Session管理等。
在spring MVC中,常用ThreadLocal保存当前登陆用户信息,这样线程在任意地方都可以取到用户信息了。