ThreadLocal
ThreadLocal
使用场景
- 每个线程需要一个独享的变量(通常是工具类,典型需要使用了类有SimpleDataFormat和Random)
- 每个线程需要保存全局变量(如在拦截器中获取哦那个户的信息),可以让不同的方法直接使用,避免传递参数的麻烦
场景一,每个线程需要独享的对象
- 每个Thread内有自己的实例副本,不共享
- 见代码
场景二 避免传递参数
![截屏2021-03-07 下午4.07.52](https://alfred-img.oss-cn-chengdu.aliyuncs.com/img/截屏2021-03-07 下午4.07.52.png)
- 见代码
使用ThreadLocal有哪些好处
- 达到线程安全
- 不用加锁,执行效率高
- 更高效的节省内存,减小开销
- 免去传参的繁琐,降低代码耦和
ThreadLocal 原理
![ThreadLocal原理图](https://alfred-img.oss-cn-chengdu.aliyuncs.com/img/ThreadLocal原理图.png)
- 每个Thread会持有一个ThreadLocalMap(一对一关系)
- 一个ThreadLocalMap存储多个ThreadLocal对
主要方法介绍
- T initialValue()
- 返回当前线程对应的初始值,这是一个延迟加载,只有调用get()后才会触发
- 当线程第一次使用get()方法访问变量时将调用此方法,除非在此之前先用过了set()方法,则不会调用initialValue()
- 通常线程只调用一次此方法就够了,单数如果调用了remove()再用get()就会重新调用此方法
- void set(T t)
- 为这个线程设置一个新值
- T get()
- 得到这个线程对应的值,如果首次调用,会使用initialValue()来获取值
- void remove()
- 删除这个线程对应的值
ThreadLocalMap类
ThreadLocalMap 即 Thread.threadlocals
ThreadLocalMap类是每个Thread类里面的变量,里面最重要的是一个键值对数组
Entry[] table
可以认为是一个map,键值对:- 键:这个ThreadLocal
- 值:实际需要的成员变量
Entry[] table
类似于hashmap,但是实际处理还是有不同- 冲突:HashMap用链表或红黑树解决冲突,ThreadLocalMap采用线性探测法,如果冲突就找下一个位置
Value的泄露
ThreadLocalMap的每个Entry都是对key弱引用,同时,对value强引用
![截屏2021-03-07 下午5.10.00](https://alfred-img.oss-cn-chengdu.aliyuncs.com/img/截屏2021-03-07 下午5.10.00.png)
- 因为value和Thread之间的强引用还存在,所以value无法被回收,就可能导致oom
- JDK已经考虑到这个问题,所以在set(), remove(), rehash()等方法中会扫描key为null的entry,并把对应的vale设为null,这样value对象就可以被回收
- 但是如果一个ThreadLocal不被使用,则其方法也不会被调用,同时线程又不停止的话,就会发生内存泄露
如何避免内存泄露(阿里规约)
- 调用remove方法就可以删除对应的entry对象,可以避免内存泄露,所以使用完ThreadLocal后应该调用remove方法
- 如果是拦截器的方法,那么拦截请求时创建,也应该拦截请求退出前销毁
注意空指针异常
如声明一个Long的ThreadLocal,还没有set就get,同时想作为long直接返回,就会发生空指针异常,因为在Long到long到拆箱过程中出错了。
注意共享对象
如果放进ThreadLocal的是一个共享变量(如static),那么多个线程取得得还是那个共享变量,还是会有并发访问问题
优先使用框架支持的
例如在Spring中,可以使用RequestContextHolder、DateTimeContextHolder
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Alfred的小站!