一、什么是ThreadLocal?
ThreadLocal,按照字面意思翻译过来就是,线程局部/本地变量,它实现了线程之间的隔离,每个线程独享自己绑定的数据。打个比方说,在Tomcat的运行过程中,有三个用户发起请求,Tomcat收到请求后,调度了三个后台线程(分别为A、B、C),每个线程都绑定了对应请求的requestId,所以线程A在运行期间只能访问自己的requestId,它是无法访问到线程B和线程C的requestId。
二、ThreadLocal的用法?
public static void main(String[] args) {
try{
ThreadLocalVariable.TL_1.set(1);
}finally{
ThreadLocalVariable.TL_1.remove();
}
}
三、ThreadLocal如何做到线程隔离?
public class ThreadLocal<T> {
// ...
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
}
1、Thread类有个ThreadLocalMap类型的变量threadLocals,那么为什么ThreadLocalMap要定义在ThreadLocal类?。
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
a、结合Thread.threadLocals和ThreadLocal.ThreadLocalMap的注释说明,可以知道�Thread类本身只声明threadLocals,并不维护它,维护threadLocals的逻辑统一放在ThreadLocal.ThreadLocalMap中。
b、ThreadLocalMap是ThreadLocal的静态内部类,它的访问权限是package级别,Thread和ThreadLocal都位于java.util.lang下,所以Thread类中是可以直接通过ThreadLocal.ThreadLocalMap来声明threadLocals的,不信你换个包试试看看有编译错误没有。
2、ThreadLocalMap是一个Map,把threadLocal本身作为Key,要绑定的变量作为Value,那么为什么不直接使用Thread对象作为Key呢?
static class ThreadLocalMap {
// ...
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
a、上面我们示例的时候,是将用户的请求requestId绑定到线程上,如果使用Thread对象作为Key,那么我们还需要绑定用户的身份信息要怎么办?使用Thread对象作为Key,后面的会覆盖前面的requestId呢,因为Map.put(key, val)时,同一个Key值是覆盖插入的。
b、Entry继承了WeakReference,是为了是Entry的Key变成弱引用,而不是让Entry本身成为弱引用对象。
3、假设我现在还要绑定用户的身份信息到线程中去,应该如何做?
在新增一个ThreadLocal对象就可以啦,如下:
static ThreadLocal<String> request_TL = new ThreadLocal<>();
static ThreadLocal<UserDTO> user_TL = new ThreadLocal<>();
如果对request_TL和user_TL设置过值,那么Thread对象的ThreadLocalMap应该有两个元素了。
4、ThreadLocalMap是如何解决Hash冲突的?
ThreadLocalMap虽然也是通过数组来实现哈希表的,但是既没有链表,也没有红黑树,而是通过开放定址法来解决Hash冲突的。
5、为什么ThreadLocalMap的Key要使用弱引用,WeakReference?
为了尽最大可能避免内存泄漏。既然垃圾回收器进行垃圾回收的时候,肯定会回收弱引用对象,那么为什么不是直接避免了内存泄漏呢?
上图左侧黄色的地方是指引用,右侧是指堆中的对象。
JVM在进行垃圾回收的时候,会采用可达性分析算法判定一个对象是否是垃圾对象。通常的ThreadLocal对象声明为静态变量(强引用),那么它是GC Root,所以ThreadLocal对象一般不会被回收。ThreadLocal对象没有持有ThreadLocalMap的引用,而是Thread对象持有。所以假设ThreadLocalMap内部的Entry的Key设计成强引用的话,在线程的生命周期比较长,并且要绑定到线程的局部变量比较多(比如秒杀场景)的情况下,是存在内存泄漏的隐患的。但是ThreadLocaMap在设计的时候,会自动做一些优化处理,比如检测到Entry的Key为null时,将对应的Value置为null,这样与垃圾回收配合就尽量避免了内存泄漏。这些优化处理在set、get、remove的时候都会触发。
6、为什么阿里巴巴开发手册规定,在使用完ThreadLocal之后要手动remove?
同上,最大可能避免内存泄漏。
四、ThreadLocal的使用场景有哪些?
1、Spring的声明式事务处理,将数据库的Connection设置到ThreadLocal中。
2、log4j2或者logback日志组件的MDC功能。
五、使用ThreadLocal带来的隐患?
1、忘记手动remove,会导致内存泄漏。