ThreadLocal threadLocal = new ThreadLocal<>();
threadLocal.set(1);
1,threadLocal这种存储方式,内部只能存放一个引用(引用可以指向容器,比如map,或者integer这种单个的)
2,threadLocal的内部实现,是通过获取当前线程,再从当前线程中得到线程上存储的线程map ThreadLocalMap
1,ThreadLocalMap的key是当前实例化的threadLocal的地址,值是存进去的object类型
2,map内部的实现,是一个初始化为16,扩容阈值为16*0.75+1的entity数组
3,entity内部是一个reference(引用) +object的格式 reference是key,object是值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在set值的时候,会先去取得当前线程,以及线程内部存储的ThreadLocalMap格式的map,再往map里设值,如果当前线程还没有被其他的threadLocal给做过设值动作,map就没有被初始化,就先进行ThreadLocalMap初始化(类似hashMap)
private void set(ThreadLocal> key, Object value) {
...
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
在set中,会先通过计算key.hashcode&15的位置来取出一个合适的位置去放置当前线程的引用.
从这个位置开始往下遍历
如果值不为空,进去校验,如果当前entity的key==我的key,替换值
如果当前的entity不为空,但是entity内的引用已经是null,说明资源被释放了,通过replaceStaleEntry去进行设值操作
最后,如果找到一个空节点,创建新的entity,去放置这个k+v
并且判断是否需要进行rehash操作(扩容)
private void replaceStaleEntry(ThreadLocal> key, Object value,
int staleSlot) {
...
//slotToExpunge = 从当前staleSlot往前,寻找到最前面的那个k为null的index
...
for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
//从当前节点staleSlot往下寻找,如果k==key,进行值覆盖后,再把key的位置放到数组的前面一点的位置上去(slotToExpunge)
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
//如果最前面的垃圾点位置和传入的位置是一样的,那么就可以把垃圾点位置更新到当前的i节点了,然后去进行垃圾节点清除的操作
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
//如果往前没找到,那么slotToExpunge == staleSlot成立,那么slotToExpunge就会往后走,直到到达最后一个参数的时候,k还是null,这时候的slotToExpunge应该等于map.length
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
//如果循环完了,确定原先不存在了,就新增,新增的位置就是最开始找到的那个,"最前面"的空位
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
//只要slotToExpunge!=staleSlot,那么要么是在staleSlot前面(之前循环得到的),要么是在staleSlot后面(通过 slotToExpunge = i得到的),就能判断出,当前数组中,还有垃圾数据,于是执行一次性清理的逻辑 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
//释放内存
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// 往下继续寻找,继续释放其他内存
Entry e; int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
if (k == null) {
...
//释放内存
} else {
int h = k.threadLocalHashCode & (len - 1);
//如果当前k的hash值不等于i,意味着这个位置不是最合适的位置,所以先置空这个i
//然后这个e还是持有的,通过找到hash所在位置,(等于是往回一些,从该位置开始,往后遍历,寻找到更贴近适合点的位置,这样做能够保证,这之前没有空档(null的槽)
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
//返回最前面的一个为null的槽点
return i;
}
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];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
cleanSomeSlots
从刚才得到的最前面的一个槽点开始,往下遍历,只要有废弃的引用,就干掉
并且在过程中,再次进行校正,把i=expungeStaleEntry,这样多校准遍历几次
保证没有废弃引用的存在
然后返回给上层,是否remove过数据
另一个问题,内存泄露
为什么这里非要每次都及时的cleanSomeSlots
因为在这里,线程会去引用对象,而对象所属的threadlocal早就被释放了,那么这个对象其实应该不存在了,但是没有被及时释放
get逻辑比较简单,不贴代码了
主要是,get的时候,如果map存在,就去map里找,找到就返回,在找的过程中,如果发现废弃槽点,就进行清理
如果找不到,就返回null
如果map不存在,就设置默认值null进map,并且返回默认值
initialValue的方法为protected,可以被复写