ThreadLocal
使用场景
- 每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random)
- 如果每个线程new一个工具类的话浪费资源
- 如果使用静态变量可能会导致线程安全问题
- 如果使用加锁等方式会影响性能
- ThreadLocal泛型中传入需要的工具类类型然后重写initialValue()方法
- 每个线程内需要保存全局变量(例如拦截器中获取用户信息),可以让不同方法直接使用,避免参数传递麻烦
- 强调的是同一个请求内(同一个线程内)不同方法间的共享
- 不需要重写initialValue()方法,但必须手动调用set()方法
ThreadLocal的两个作用
- 让某个需要用的对象在线程间隔离(每个线程都有自己的独立对象)
- 在任何方法中都可以轻松获取该对象
ThreadLocal的4点好处
- 达到线程安全
- 不需要加锁,提高执行效率
- 更高效地利用内存、节省开销:本来是一个任务新建一个工具类,使用ThreadLocal后每个线程新建一个工具类就行
- 免去传参的繁琐,同时也使代码耦合度更低
Thread、ThreadLocal、ThreadLocalMap三者关系
- 每个Thread对象中都有一个ThreadLocalMap成员变量
- 一个ThreadLocalMap能存储多个ThreadLocal
initialValue()
- 该方法会返回当前线程对应的“初始值”,这是一个延迟加载的对象,只有调用get的时候才会触发
- 当线程第一次使用get方法访问变量时,将调用此方法,除非线程先前调用了set方法,这种情况下,不会为线程调用本initialValue方法
- 通常每个线程最多调用一次此方法,但是如果已经调用了remove()后,调用get(),则可以再次调用此方法
- 如果不是重写本方法,此方法会返回null,一般使用匿名内部类的方法来重写initialValue()方法,以便在后续使用中可以初始化副本对象
ThreadLocal主要方法
- initialValue():初始化
- set():为这个线程设置一个新值
- get():得到这个线程对应的value,如果首次调用get(),则会调用initialValue来得到这个值
- get方法会先取出当前线程的ThreadLocalMap,然后调用map.getEntry,把本ThreadLocal的引用作为参数传入,取出map中属于本ThreadLocal的value
- 注意,map以及map中的key和value都是保存在线程中,而不是ThreadLocal中
- remove():删除对应这个线程的值
ThreadLocal注意点
- 可能会导致内存泄漏
- ThreadLocalMap中每个Entry都是一个对key的弱引用,但是value是强引用
- 正常情况下,当线程终止,保存在ThreadLocal中的value会被垃圾回收,因为没有强引用了,但是如果线程不终止(比如线程需要保持很久),那么key对应的value就不能被回收
- 因为value和Thread之间还存在强引用链路,导致value无法回收就可能出现OOM
- JDK考虑到了这问题,在使用set,remove,rehash方法中会扫描key为null的Entry,则把对应的value设置为null,这样就会回收,但是如果不掉用这些方法,就不会清除了
- 避免:使用完了之后主动调用remove
- 空指针异常
- 如果一开始指定一个包装类泛型,返回时使用一个基本类型,在取数据时就会经历拆箱的国产,如果没有给初始值就会报NPE异常
- 不要在ThreadLocal中存放静态变量
- 如果可以不使用ThreadLocal就解决问题那么就不要强行使用
- 例如任务数量很少的时候,在局部变量中新建对象就可以解决问题,就不需要使用到ThreadLocal
- 有限使用框架支持的,而不是自己创造的
- Spring:如果可以使用RequestContextHolder,那么就不需要自己维护ThreadLocal,因为可能会忘记remove()导致内存泄漏
- 每次HTTP请求都对应一个线程,线程之间相互隔离,这就是ThreadLocal的典型应用场景







