ThreadLocal 是一个关于创建线程局部变量的类,这个变量只能当前线程使用,其他线程不可用。 ThreadLocal 提供 get()和 set()方法创建和修改变量。
怎么使用
ThreadLocal 有三种使用方式:
1 |
|
1 |
|
1 |
|
get(),set()
查看 ThreadLocal 中的 get(),set() 中有一个 ThreadLocalMap 对象
1 |
|
ThreadLocalMap
ThreadLocalMap 就是一个内部静态类,没有继承也没有接口,是一个自定义的 Hash 映射,用户维护线程局部变量。
1 |
|
ThreadLocalMap 的内部类 Entry,继承 WeakReference 弱引用
1 |
|
ThreadLocalMap 中存放线程局部变量的数据结构
1 |
|
小结:
- ThreadLocal ——> ThreadLocalMap——> Entry[]
- Entry 维护一个 ThreadLocal 作为 key,value 对应 ThreadLocal 的值
初始化方法
1 |
|
小结:
- ThreadLocalMap 默认容量为 16,每次计算索引位置会加 0x61c88647 然后和长度-1 取模
- 索引是原子类
Entry 的 get
1 |
|
小结:
- get 方法中先计算索引位置,如果 key 相同则返回,不同则用线性探测法取出,当 key 为 null 的时候清理 i 所在位置直到不为 null 的数据。如果找不到 key 的数据则返回 null
Entry 的 Set
1 |
|
小结:
1.计算索引位置 2.如果当前位置有值则索引+1 判断是否为空,不为空继续+1,直到找到位置插入 3.size+1 4.是否清理 key 为 null 的数据,如果没有被清理&& size 大于列表长度的 2/3 则扩容
清理 key 关联的对象被回收的数据
1 |
|
expungeStaleEntry 方法
1 |
|
小结:
- 从 staleSlot 开始,清除 key 为 null 的 Entry,并将不为空的元素放到合适的位置,最后遍历到 Entry 为空的元素时,跳出循环返回当前索引位置
rehash 方法
1 |
|
小结:
- 调用 expungeStaleEntries 方法,清理整个 table 中 key 为 null 的 Entry
- 如果清理后 size 超过阈值的 1/2,则进行扩容。
- 新表长度为老表 2 倍,创建新表。
- 遍历老表所有元素,如果 key 为 null,将 value 清空;否则通过 hash code 计算新表的索引位置 h,如果 h 已经有元素,则调用 nextIndex 方法直到寻找到空位置,将元素放在新表的对应位置。
- 设置新表扩容的阈值、更新 size、table 指向新表
缺点
内存泄露
从 Entry 源码中可以看出,Entry 继承了 WeakReference 弱引用,如果外部没有引用 ThreadLocal,则 Entry 中作为 Key 的 ThreadLocal 会被销毁成为 null,那么它所对应的 value 不会被访问到。当线程一直在执行&&没有进行 remove,rehash 等操作时,value 会一直存在内存,从而造成内存泄露
总结
- Thread 中都有一个 ThreadLocalMap
- ThreadLocalMap 的 key 是 ThreadLocal 实例
- 默认容量大小为 16,当 size 超过 2/3 容量&&没被清理就 rehash,
- 当 size 超过扩容因子 3/4 的时候扩容为原来的 2 倍
- 当发现一个 key 为 null 的时候,会进行清理,直到下一个 key 不为 null
- has 冲突的解决方法和 hashMap 不相同,ThreadLocal 是找这个冲突索引的下一个元素直到找到,hashMap 是转换为红黑树