属性字段
java
//哈希码
private final int threadLocalHashCode = nextHashCode();
//下一个线程的哈希码
private static AtomicInteger nextHashCode =
new AtomicInteger();
//增量值
private static final int HASH_INCREMENT = 0x61c88647;
ThreadLocal使用开放地址法伪随机探测的解决方式,通过魔数0x61c88647(黄金分割数*2^32)作为步长优化哈希分布,来解决哈希冲突
内部类
Entry
Entry是ThreadLocal的存储单元,实际存储数据是一个个Entry
scala
static class Entry extends WeakReference<ThreadLocal<?>> {
//值
Object value;
Entry(ThreadLocal<?> k, Object v) {
//key 弱引用
super(k);
value = v;
}
}
Entry继承弱引用(WeakReference),当发生垃圾回收的时候回收key,防止key一直被引用造成大量的key堆积形成内存溢出;当key被垃圾回收时,value还是存在内存中的,大量的value堆积也会造成内存溢出,正确的做法是使用完了显示释放(remove())。
ThreadLocalMap
1. 属性字段
arduino
//初始容量
private static final int INITIAL_CAPACITY = 16;
//数据
private Entry[] table;
//大小
private int size = 0;
//阈值
private int threshold; // Default to 0
2.构造方法
构建一个初始,包括第一个key和第一个value
scss
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始数组
table = new Entry[INITIAL_CAPACITY];
//定位槽位
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//赋值
table[i] = new Entry(firstKey, firstValue);
//初始size为1
size = 1;
//设置阈值为容量的2/3
setThreshold(INITIAL_CAPACITY);
}
私有构造方法,线程在初始化的时候通过ThreadLocal.createInheritedMap()调用,把父线程的inheritableThreadLocals变量拷贝到子线程的inheritableThreadLocals的变量中,这样子线程可以可见父线程的数据,InheritableThreadLocal实现的关键点
ini
private ThreadLocalMap(ThreadLocalMap parentMap) {
//父线程的数据
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
//取得还是父线程的值
Object value = key.childValue(e.value);
//构造Entry
Entry c = new Entry(key, value);
//计算槽位
int h = key.threadLocalHashCode & (len - 1);
//哈希冲突,循环找空位置
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
核心方法
set(T value)
scss
public void set(T value) {
//当前线程
Thread t = Thread.currentThread();
//获取ThreadLocalMap,保存在线程对象里面
//线程首次进来是空的
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
向ThreadLocal里插入值
ini
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//定位数组下标
int i = key.threadLocalHashCode & (len-1);
//循环找到没有被占用的槽位
for (Entry e = tab[i];
e != null;
//采用线性探测方式解决哈希冲突
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//key相同,覆盖value
if (k == key) {
e.value = value;
return;
}
//为空,k被垃圾回收了,因为k是弱引用
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//空槽位放入
tab[i] = new Entry(key, value);
//size加一
int sz = ++size;
//不存在无效Entry,并且大小大于等于阈值进行
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
下个槽位
arduino
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
替换无效槽位
ini
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//从当前索引向上找,找到第一个key为空的槽位停止,更新slotToExpunge
for (
//上一个槽位
int i = prevIndex(staleSlot, len);
//
(e = tab[i]) != null;
i = prevIndex(i, len))
//key是空的
if (e.get() == null)
slotToExpunge = i;
for (
//下一个槽位
int i = nextIndex(staleSlot, len);
//不为空说明存在哈希冲突
(e = tab[i]) != null;
//继续看下一个槽位
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//key相同
if (k == key) {
//覆盖value
e.value = value;
//交换i和staleSlot,把set方法哈希冲突key为空的槽位和从此槽位向下找存在哈希冲突key值 //相等的槽位交换
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//相等说明,向上找没找到key为空的槽位
if (slotToExpunge == staleSlot)
//更新为向下找key相等的槽位
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//k被回收,向上找没找到key为空的槽位
if (k == null && slotToExpunge == staleSlot)
//更新为i
slotToExpunge = i;
}
//在上面循环中没找到key,构造新的Entry放入即可
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// 不相等说明还有存在要清除的Entry,清除
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
清除无效槽位
ini
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 清除value
tab[staleSlot].value = null;
//清除Entry
tab[staleSlot] = null;
//长度减一
size--;
// Rehash until we encounter null
Entry e;
int i;
//向后找,循环中的都是存在哈希冲突的
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//key已被垃圾回收
if (k == null) {
//清除value
e.value = null;
//清除Entry
tab[i] = null;
//长度减一
size--;
}
//冲突的槽位重新分配槽位
else {
//重新计算槽位
int h = k.threadLocalHashCode & (len - 1);
//不是当前槽位(理想槽位)
if (h != i) {
//清空当前Entry
tab[i] = null;
//向后找哈希不冲突的位置
while (tab[h] != null)
h = nextIndex(h, len);
//复制
tab[h] = e;
}
}
}
return i;
}
清除槽位
ini
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
//无效Entry
if (e != null && e.get() == null) {
n = len;
removed = true;
//清除无效Entry返回有效的槽位
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
获取ThreadLocalMap
javascript
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
创建ThreadLocalMap
javascript
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
重新哈希
scss
private void rehash() {
//清除所有无效Entry
expungeStaleEntries();
//size>=阈值-阈值/4
if (size >= threshold - threshold / 4)
//扩容
resize();
}
扩容
ini
private void resize() {
//原数组
Entry[] oldTab = table;
//原长度
int oldLen = oldTab.length;
//新长度为原长度的2呗
int newLen = oldLen * 2;
//构造新的Entry数组
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
//无效Entry
if (k == null) {
//清除value
e.value = null; // Help the GC
} else {
//重新计算槽位
int h = k.threadLocalHashCode & (newLen - 1);
//循环直到找到哈希不冲突的槽位
while (newTab[h] != null)
h = nextIndex(h, newLen);
//放入新数组
newTab[h] = e;
count++;
}
}
}
//设置阈值
setThreshold(newLen);
//设置size
size = count;
//更新原数组
table = newTab;
}
set方法逻辑总结:
- 获取当前线程的ThreadLocalMap对象;
- 为空。创建ThreadLocalMap对象,初始Entry table默认容量为16,计算槽位构建Entry放入数组中,设置阈值为初始容量的2/3;
- 不为空。先计算槽位,如果有哈希冲突,判断是否是同一个ThreadLocal对象(key是否相同)。相同的话更新value值,结束;key为空,替换无效的Entry,结束;
- 没有哈希冲突放入值即可;
- 清除无效槽位,判断是否需要扩容,需要的话进行扩容。
在set方法中会清除无效的Entry,整理Entry数组,扩容的时候也会清除无效的Entry,从而保证Entry数组的整洁,减小垃圾残留。
get()
java
public T get() {
//当前线程
Thread t = Thread.currentThread();
//当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//不为空返回结果
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//初始化value
return setInitialValue();
}
csharp
private Entry getEntry(ThreadLocal<?> key) {
//计算槽位
int i = key.threadLocalHashCode & (table.length - 1);
//结果
Entry e = table[i];
//不为空,key相等返回结果
if (e != null && e.get() == key)
return e;
else
//向后找key相等的Entry
return getEntryAfterMiss(key, i, e);
}
ini
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//循环向后找直到key相等,返回结果
while (e != null) {
ThreadLocal<?> k = e.get();
//key相等返回
if (k == key)
return e;
//k为空清除无效的Entry
if (k == null)
expungeStaleEntry(i);
else
//下一个槽位
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
scss
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//下面的逻辑和set方法一样
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
//是TerminatingThreadLocal类型,注册。
//用于自定义清理资源
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
//返回初始化的值
return value;
}
csharp
//子类可扩展,默认返回空
protected T initialValue() {
return null;
}
get方法逻辑总结:
- 获取当前线程的ThreadLocalMap对象;
- 不为空。计算哈希值,获取槽位,根据槽位获取Entry。Entry不为空并且key相等,返回value值,结束;Entry为空向后查找不为空的Entry(空的Entry清除掉),并且key相等返回value,结束;
- 为空。走初始化value逻辑。执行initialValue() ,把初始化值设置进ThreadLocal中,返回结果。