线程局部缓存:为线程缓存数据,将数据本地化(脱离共享)
原理:
-
每个线程有一个ThreadLocalMap属性,本质就是一个map
-
map里面存储的<key, value>称为键值对,存储键值对时需要先求取哈希值
由于哈希值会出现冲突,所以会造成“错位”元素的出现(元素“理想位置”和实际存储位置不一样)
“理想位置”是指该ThreadLocal对象初次计算出的哈希值
如果从“理想位置”到实际存储位置是连续的,这里称该序列是“紧凑”的
- map里存储的key是一个弱引用,其包装了当前线程中构造的ThreadLocal对象
这意味着,只要ThreadLocal对象丢掉了强引用,那么在下次GC后,map中的ThreadLocal对象也会被清除
对于那些ThreadLocal对象为空的map元素,这里称其为【垃圾值】,稍后会被主动清理
-
map里存储的value就是缓存到当前线程的值,这个value没有弱引用去包装,需要专门的释放策略(如果key为null进行回收)
-
一个线程对应多个ThreadLocal,一个ThreadLocal只对应一个值
概念
-
ThreadLocal
作为线程副本当中的Key(弱引用), 调用线程当中的ThreadLocalMap获得实际的value
-
ThreadLocalMap
每个Thread中threadLocals中存储副本Map
-
Entry
继承自WeekReference. Map中存储的值. ThreadLocal当中Key.
重要参数
ThreadLocal
1 |
// 每次实例化生成HashCode值, 用于定位solt |
ThreadLocalMap
1 |
// Map初始容量,必须为2的冪 |
重要操作
ThreadLocal
get
- 获取线程当存储的ThreadLocalMap
- 通过ThreadLocal读取ThreadLocalMap当前的Entry
- 返回Entry中的值(return)
- 如果线程不存在Map或者未设置Entry.则创建Map和创建Entry
1 |
public T get() { |
set
1 |
public void set(T value) { |
ThreadLocalMap
nextIndex(槽位置, 最大长度)
哈希值发生冲突时,计算下一个哈希值。此处使用线性探测寻址,只是简单地将索引+1扩容机制
prevIndex(槽位置, 最大长度)
线性探测,但是逆方向进行,即向前遍历. 简单地将索引-1扩容机制
expungeStaleEntry(槽位置)
删除槽位. 如果删除的槽位之前发生过冲突, 将重新恢复到理想位置
cleanSomeSlots(槽位置, 结束位置)
清除范围内的垃圾值(key被回收)
getEntryAfterMiss(key, 槽位, 实体)
遍历查找实体对象(边遍历边删除垃圾数据)
getEntry(key)
1 |
private Entry getEntry(ThreadLocal<?> key) { |
rehash()
删除所有垃圾值, 如果当前容量大于3/4阀值将进行扩容
1 |
private void rehash() { |
resize()
进行2倍扩容(重新进行hash插入新槽中)
1 |
private void resize() { |
set(key, val)
1 |
private void set(ThreadLocal<?> key, Object value) { |