0.什么是ThreadLocal
线程本地变量,线程间读写同一个ThreadLocal实例是线程隔离的。
1.实现方式
Thread类有一个threadLocals:ThreadLocalMap
实例对象,每个thread操作自己的map以隔离。
2.主要数据结构
2.1 ThreadLocalMap
- 实质是一个
table:Entry[]
数组,由ThreadLocal实例的hash&(table.length-1)
决定index
为了高效求余,
tablen.length
只能是2的幂,所以扩容时只能继续是2的幂,实现中采用2倍扩容
- 哈希冲突的解决:线性预测法(linear probing)
2.2 ThreadLocalMap.Entry
-
extends WeakReference
然后添加变量value
存储值。
3.常用api
3.1 构造函数
懒初始化,构造函数什么也不做,只有在实际使用的时候如果没初始化才会初始化。
// 节选set部分初始化代码
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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;
setThreshold(INITIAL_CAPACITY);
}
3.2 set方法
- 计算出hash值,如果不冲突,直接存入
- 如果冲突,向后探测,存入
- 如果存入后容量到达阈值(
capacity*2/3
),扩容
3.3 get方法
- 算出hash值,如果当前entry的
reference
是当前的ThreadLocal实例,直接取出,否则向后探测
3.4 remove方法
清理指定位置
3.5 扩容
发生的时间:如果set完后size大于阈值,做一次全量清理,如果清理完后,size大于阈值的3/4,则进行扩容。
扩容后会重新计算hash存入新的新的数组。
/**
* Double the capacity of the table.
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
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();
if (k == null) {
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 = count;
table = newTab;
}
3.6 其他
由于Entry是继承WeakReference类的,所以有可能存在GC导致索引失效的问题,在get,set,resize时都会进行一定程度的清理。
在清理时如果当前位置被清理了,还会检查其后的位置,因为有可能是因为hash冲突存过去的,如果是这种情况,会将元素重新找到位置存储。
4.有意思的地方
4.1 位运算求余
这种方法只适用于对2的幂求余
4.2 魔数0x61c88647
参考2中对魔数的原理进行了说明。
4.3 android版本
在android版本的实现略有不同
-
reference
作为一个实例变量 - 数组改为
capacity*2
大小,使用偶数位置存放索引,+1位置存放value - 相应的,index取值要保证每次算出的均为偶数,所以hash计算的时候是
魔数*2
增加
//java的实现
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//android的实现
/**
* Internal hash. We deliberately don't bother with #hashCode().
* Hashes must be even. This ensures that the result of
* (hash & (table.length - 1)) points to a key and not a value.
*
* We increment by Doug Lea's Magic Number(TM) (*2 since keys are in
* every other bucket) to help prevent clustering.
*/
private final int hash = hashCounter.getAndAdd(0x61c88647 * 2);
4.4 注意
类的注释中有这么一句,通常ThreadLocal实例是private static 类型。
{@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
5.参考
- ThreadLocal源码
build 1.8.0_121-b13
版本- https://www.cnblogs.com/micrari/p/6790229.html
- https://www.cnblogs.com/dolphin0520/p/3920407.html
网友评论