1. ThreadLocal是什么?
ThreadLocal叫做线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal在每个线程中都为变量创建了一个副本,那么每个线程都可访问自己内部的变量副本。
- 每个线程内都有自己的变量副本,且该副本只能由当前线程使用。也就不存在多线程间共享的问题。
2. Thread、ThreadLocal、ThreadLocalMap之间的关系
- 每个线程内部都有一个类型是
ThreadLocalMap的变量。 2.ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value)。 - 线程内部的
ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向ThreadLocalMap获取和设置线程的变量值。 - 对于不同的线程,每次获取变量值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
3. ThreadLocal的原理是什么?
ThreadLocal的实现原理主要依赖线程的本地变量表。每个线程在创建时都会分配一个本地变量表,用于存储线程特有的数据。当线程访问一个 ThreadLocal 变量时,ThreadLocal 会根据当前线程的标识从本地变量表中获取对应的变量副本。如果本地变量表中不存在该变量副本,则会创建一个新的副本并存储到本地变量表中。由于每个线程都有自己的本地变量表,因此 ThreadLocal 可以确保每个线程都操作自己的变量副本,从而避免了线程间的数据竞争和同步问题。
4. ThreadLocal的常见使用场景有哪些?
ThreadLocal适用于以下两种场景:
- 每个线程需要有自己独立的变量实例。
- 该变量实例需要在多个方法中被使用,但不希望被多线程共享。
对于第一点,每个线程拥有自己实例,实现它的方式很多。例如可以在线程内部构建一个单独的实例。ThreadLoca 可以以非常方便的形式满足该需求。
对于第二点,可以在满足第一点(每个线程有自己的实例)的条件下,通过方法间引用传递的形式实现。ThreadLocal 使得代码耦合度更低,且实现更优雅。
4.1 场景
- 存储用户session
- 数据跨层传递(controller、service、dao)
每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。
例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等)。在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。
5. 什么情况下ThreadLocal会发生内存泄漏?
- ThreadLocal使用完了但没有手动删除。
- 当前线程依然运行,实际使用ThreadLocal场景都是采用线程池,而线程池中的线程都是复用的。
由于ThreadLocalMap 的生命周期跟 Thread 一样长,对于重复利用的线程来说,如果没有手动删除(remove()方法)对应 key 就会导致entry(null,value)的对象越来越多,从而导致内存泄漏.
6. 为什么ThreadLocal会发生内存泄漏问题?
ThreadLocalMap中使用的key为ThreadLocal的弱引用。弱引用的特点是:如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然被清理掉。
所以如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候会被清理掉。这样一来,ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,就会出现 key 为 null 的 value。
ThreadLocal其实是与线程绑定的一个变量,如此就会出现一个问题:如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例子,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀。
6.1 为什么 key 要用弱引用?
在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null(也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么会把 value 置为 null 的。这就意味着使用threadLocal , 当前线程依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocal 调用 get()/set()/remove() 中的任一方法的时候会被清除,从而避免内存泄漏.
7. 如何正确使用ThreadLocal?
- 将ThreadLocal变量定义成
private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。 - 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。