我们对ThreadLocal的两个主要公共方法set和get方法进行分析:
一、首先看下set方法:
public void set(T value) {
Thread t = Thread.currentThread();//根据当前线程获取ThreadLocal的内部类ThreadLocalMap实例
ThreadLocalMap map = getMap(t);
if (map != null)
//调用ThreadLocalMap的set方法,由此看见值的存储是由ThreadLocalMap完成的
map.set(this, value);
else
createMap(t, value);
}
由上可知:实际核心操作是调用了ThreadLocalMap.set()方法:
private void set(ThreadLocal key, Object value) {
//Entry是一个继承了WeakReference弱引用的内部类
Entry[] tab = table;
int len = tab.length;
//根据ThreadLocal的hasCode进行一次散列计算得到当前key应该存储的位置
int i = key.threadLocalHashCode & (len-1);
//遍历,每次读取下一个位置的元素
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
//遇到一致的ThreadLocal,则覆盖旧值并返回
if (k == key) {
e.value = value;
return;
}
//遇到为null的ThreadLocal,则清理陈旧的元素并将新值正确存储
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//如果上面的遍历过程中没有遇到一致的ThreadLocal并且也没有遇到为null的ThreadLocal
//则新建一个Entry元素,并存入table数组中
tab[i] = new Entry(key, value);
//数组大小自增1
int sz = ++size;
//判定是否需要重新整理数组
//rehash方法会清理数组中无用的元素,然后判定是否需要进行重新扩容,扩容是原来的两倍
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
我们看下replaceStaleEntry(ThreadLocal key, Object value,int staleSlot)这个方法:
private void replaceStaleEntry(ThreadLocal key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
//申明一个局部变量标记陈旧位置
int slotToExpunge = staleSlot;
//遍历,从staleSlot开始,每次读取上个位置的元素
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
//遇到Entry中的ThreadLocal为null,则重置标记位
if (e.get() == null)
slotToExpunge = i;
//遍历,从staleSlot开始,每次读取下个位置的元素
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal k = e.get();
//遇到一致的ThreadLocal,则覆盖旧值,并将数组中tab[i]和tab[staleSlot]两个位置上的元素互换
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//检查标记位是否修改,如果没有修改则重置标记位为i
if (slotToExpunge == staleSlot)
slotToExpunge = i;
//清理标记位
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//遇到Entry中的ThreadLocal为null,并且标记位自初始化之后未发生过改变,则重置标记位
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
//如果经过上述步骤仍没有将值正确存储,则新建一个Entry元素,存入tab[staleSlot]中
tab[staleSlot] = new Entry(key, value);
//检查标记位是否自初始化之后未发生过改变,如果发生改变,则清理标记位
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
接下来看下rehash()方法:
private void rehash() {
//对数组中所有元素进行遍历,并判断是否需要清理
expungeStaleEntries();
//判断当前数组大小是否高于阈值的3/4,如果高了,则进行扩容
if (size >= threshold - threshold / 4)
resize();
}
扩容方法:resize():
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) {
//元素的值是与线程强关联的,只会伴随线程的结束才会被GC,所以这里将value=null,可以帮助GC
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;
}
二、继续分析get方法:
public T get() {
Thread t = Thread.currentThread();
//根据ThreadLocal找到对应的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据ThreadLocal从ThreadLocalMap找到对应的Entry元素
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
//Entry元素中的value即为当前线程的变量副本
return (T)e.value;
}
return setInitialValue();
}
接着看下getEntry()方法:
private Entry getEntry(ThreadLocal key) {
//进行一次哈希算法得到对应key的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//如果根据哈希映射得到的变量为空或key值不一致,则走该流程
return getEntryAfterMiss(key, i, e);
}
接着看下getEntryAfterMiss()方法:
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
//如果Entry不为null,则继续轮询查找key一致的元素
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
//当轮询时发现有key==null的元素则调用清理操作
expungeStaleEntry(i);
else
//取下一个元素
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
总结:
1、分析set方法的过程中我们发现ThreadLocal中实际存储值的操作是由其内部类ThreadLocalMap实现,ThreadLocalMap使用Entry的方式存储每个线程对应的变量,ThreadLocal在其中扮演的角色是始终是引用的作用,根据ThreadLocal在ThreadLocalMap查找对应的Entry,在Entry中又作为一个弱引用存在。
2、ThreadLocal和Thread的从属关系实际是ThreadLocal是Thread的附属关系,在源码中多处可以看出这种关系,例如:get方法中,我们可知ThreadLocalMap属于Thread,根据ThreadLocal从ThreadLocalMap中获取Entry,由此可以看出ThreadLocal和Thread的从属关系。
3、ThreadLocal的内部类ThreadLocalMap的扩容条件是当前容量大于3/4阈值大小,阈值大小是初始值大小的2/3,初始值默认大小为16,根据的整除算法,所以默认阈值大小为10。