# ThreadLocal的使用场景

  • 场景1:每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)
  • 场景2:每个线程内需要保存全局变量(例如在拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

# ThreadLocal的两个作用

  1. 让某个需要用到的对象在线程间隔离(每个线程都有自己的独立的对象)
  2. 在任何方法中都可以轻松获取到该对象

# ThreadLocal设置值的两种方法

  1. initialValue 在ThreadLocal第一次get的时候把对象给初始化出来,对象的初始化时机可以由我们控制。
  2. set 如果需要保存到ThreadLocal里的对象的生成时机不由我们随意控制,例如拦截器生成的用户信息,用ThreadLocal.set直接放到我们的ThreadLocal中去,以便后续使用。

# 使用ThreadLocal带来的好处

  1. 达到线程安全
  2. 不需要加锁,提高执行效率
  3. 更高效地利用内存、节省开销
  4. 免去传参的繁琐:可以在任何地方直接通过ThreadLocal拿到,再也不需要每次都传同样的参数。ThreadLocal使得代码耦合度更低,更优雅

# 主要方法介绍

方法 作用
T initialValue() 初始化
void set(T t) 为这个线程设置一个新值
T get() 得到这个线程对应的value。如果是首次调用get(),则会调用initialize来得到这个值
void remove() 删除对应这个线程的值

# get方法:

get方法是先取出当前线程的ThreadLocalMap,然后调用map.getEntry方法,把本ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的value

注意,这个map以及map中的key和value都是保存在线程中的,而不是保存在ThreadLocal中

# initialValue方法:

initialValue方法是没有默认实现的,如果我们要用initialValue方法,需要自己实现,通常是匿名内部类的方式。

# remove方法:

remove方法也是先取出当前线程的ThreadLocalMap,然后删除当前ThreadLocal

# ThreadLocal注意点

# 内存泄漏

  • 什么是内存泄漏:某个对象不再有用,但是占用的内存却不能被回收

  • 产生内存泄漏的原因:

ThreadLocalMap的每个Entry 都是一个对key的弱引用,同时,每个Entry 都包含了一个对value的强引用

正常情况下,当线程终止,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用了

但是,如果线程不终止(比如线程需要保持很久),那么key对应的value就不能被回收

因为value和Thread之间还存在这个强引用链路,所以导致value无法回收,就可能会出现OOM

JDK已经考虑到了这个问题,所以在set,remove,rehash方法中会扫描key为null的Entry,并把对应的value设置为null,这样value对象就可以被回收

但是如果一个ThreadLocal不被使用,那么实际上set,remove,rehash方法也不会被调用,如果同时线程又不停止,那么调用链就一直存在,那么就导致了value的内存泄漏

  • 如何避免:在使用完ThreadLocal之后,主动调用remove方法

# 空指针异常

在ThreadLocal中要避免装箱、拆箱的操作,避免发生NPE

装箱:自动将基本数据类型转换为包装器类型

拆箱:自动将包装器类型转换为基本数据类型

# 共享对象

如果在每个线程中ThreadLocal.set()进去的东西本来就是多线程共享的同一个对象,

比如static对象,那么多个线程的ThreadLocal.get()取得的还是这个共享对象本身,还是有并发访问问题

因此在ThreadLocal中不能使用static对象,这种用法是错误的

# 如果可以不使用ThreadLocal就解决问题,那么不要强行使用

例如在任务数很少的时候,在局部变量中可以新建对象就可以解决问题,那么就不需要使用到ThreadLocal

# 优先使用框架的支持,而不是自己创造

例如在Spring中,如果可以使用RequestContextHolder,那么就不需要自己维护ThreadLocal,因为自己可能会忘记调用remove()方法等,造成内存泄漏