一、概述
官网解释:ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量,ThreadLocal实例通常来说都是private static类型。
1、总结:为每个线程创建一个单独的变量副本,提供保持对象的方法和避免参数传递的复杂性。不是为了解决多线程访问共享变量,
2、应用场景:按线程多实例(每个线程对应一个实例)的对象的访问,且这个对象很多地方都要用到。
如:同一网站登录用户,每个用户服务器会为其开一个线程,每个线程中创建一个ThreadLocal存用户基本信息,多页面跳转,频繁取用户信息,多线程间没联系且当前线程及时获取想要数据
二、实现原理

ThreadLocal可看做容器,存放着属于当前线程的变量。
1、四个对外开放接口方法,设置,访问、删除、初始化
(1) void set(Object value)设置当前线程的线程局部变量的值。
(2) public Object get()该方法返回当前线程所对应的线程局部变量。
(3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
(4) protected Object initialValue()返回该线程局部变量的初始值,protected为让子类覆盖而设计。是个延迟调用方法,线程第1次调用get()或set(Object)才执行,仅执行1次,缺省实现直接返回null。
2、如何为每个线程维护变量副本?
每个Thread 维护一个 ThreadLocalMap 映射表,key为当前ThreadLocal对象,而value对应线程的变量副本,每个线程可能存在多个ThreadLocal


ThreadLocalMap源码

三、使用场景
生成唯一事务id,并传给manager/DAO类方法,记录日志。解决方案(不好,代码冗余):事务id传给所有方法。
ThreadLocal中设置事务id解决。无论调什么方法,都从threadlocal访问事务id。同时处理多个请求,由于每个请求在框架级别上在单独的线程中处理,因此事务id对于每个线程都惟一,可从线程所有执行路径访问它
四、内存泄漏问题(参考其他博文)
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
1)Map针对key用弱引用. threadlocal为null,将被gc回收. 2)但value不能回收,因为存在一条从current thread连过来的强引用. 3)thread结束, current thread不在栈中,强引用才断开, Current Thread, Map, value将全部被GC回收.
结论:只要线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,发生内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。
泄漏根源:ThreadLocalMap生命周期跟Thread一样长,没手动删除对应key就内存泄漏,不是因为弱引用。
避免泄漏(弱引用作用):每次set和get都检测是否有key为Null在Map中,有就干掉。忘记remove,当前线程的其他ThreadLocal用时候也可被干掉。ps:建议开发者自己控制,每次用完ThreadLocal,都调remove清除数据。
四、和hashmap不同
HashMap:数组+链表实现的,
ThreadLocalMap:没有链表,entry弱引用
https://zhuanlan.zhihu.com/p/56214714
https://zhuanlan.zhihu.com/p/158684233