一、什么是ThreadLocal?

ThreadLocal,按照字面意思翻译过来就是,线程局部/本地变量,它实现了线程之间的隔离,每个线程独享自己绑定的数据。打个比方说,在Tomcat的运行过程中,有三个用户发起请求,Tomcat收到请求后,调度了三个后台线程(分别为A、B、C),每个线程都绑定了对应请求的requestId,所以线程A在运行期间只能访问自己的requestId,它是无法访问到线程B和线程C的requestId。

二、ThreadLocal的用法?

  1. public static void main(String[] args) {
  2. try{
  3. ThreadLocalVariable.TL_1.set(1);
  4. }finally{
  5. ThreadLocalVariable.TL_1.remove();
  6. }
  7. }

三、ThreadLocal如何做到线程隔离?

  1. public class ThreadLocal<T> {
  2. // ...
  3. public void set(T value) {
  4. Thread t = Thread.currentThread();
  5. ThreadLocalMap map = getMap(t);
  6. if (map != null)
  7. map.set(this, value);
  8. else
  9. createMap(t, value);
  10. }
  11. }

1、Thread类有个ThreadLocalMap类型的变量threadLocals,那么为什么ThreadLocalMap要定义在ThreadLocal类?。

  1. public class Thread implements Runnable {
  2. ThreadLocal.ThreadLocalMap threadLocals = null;
  3. ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  4. }

a、结合Thread.threadLocals和ThreadLocal.ThreadLocalMap的注释说明,可以知道�Thread类本身只声明threadLocals,并不维护它,维护threadLocals的逻辑统一放在ThreadLocal.ThreadLocalMap中。
ThreadLocal.ThreadLocalMap_usage.jpg
b、ThreadLocalMap是ThreadLocal的静态内部类,它的访问权限是package级别,Thread和ThreadLocal都位于java.util.lang下,所以Thread类中是可以直接通过ThreadLocal.ThreadLocalMap来声明threadLocals的,不信你换个包试试看看有编译错误没有。

2、ThreadLocalMap是一个Map,把threadLocal本身作为Key,要绑定的变量作为Value,那么为什么不直接使用Thread对象作为Key呢?

  1. static class ThreadLocalMap {
  2. // ...
  3. static class Entry extends WeakReference<ThreadLocal<?>> {
  4. Object value;
  5. Entry(ThreadLocal<?> k, Object v) {
  6. super(k);
  7. value = v;
  8. }
  9. }
  10. }

a、上面我们示例的时候,是将用户的请求requestId绑定到线程上,如果使用Thread对象作为Key,那么我们还需要绑定用户的身份信息要怎么办?使用Thread对象作为Key,后面的会覆盖前面的requestId呢,因为Map.put(key, val)时,同一个Key值是覆盖插入的。
b、Entry继承了WeakReference,是为了是Entry的Key变成弱引用,而不是让Entry本身成为弱引用对象。

3、假设我现在还要绑定用户的身份信息到线程中去,应该如何做?
在新增一个ThreadLocal对象就可以啦,如下:

  1. static ThreadLocal<String> request_TL = new ThreadLocal<>();
  2. static ThreadLocal<UserDTO> user_TL = new ThreadLocal<>();

如果对request_TL和user_TL设置过值,那么Thread对象的ThreadLocalMap应该有两个元素了。

4、ThreadLocalMap是如何解决Hash冲突的?
ThreadLocalMap虽然也是通过数组来实现哈希表的,但是既没有链表,也没有红黑树,而是通过开放定址法来解决Hash冲突的。

5、为什么ThreadLocalMap的Key要使用弱引用,WeakReference?
为了尽最大可能避免内存泄漏。既然垃圾回收器进行垃圾回收的时候,肯定会回收弱引用对象,那么为什么不是直接避免了内存泄漏呢?
ThreadLocal.png
上图左侧黄色的地方是指引用,右侧是指堆中的对象。
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,会导致内存泄漏。