ThreadLocal源码解析过程
我们来看看set过程的源码
public void set(T value) {
//获取线程t
Thread t = Thread.currentThread();
//根据线程来获取对应的ThreadLoaclMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//这里是拿Thread类里面的ThreadLocal.ThreadLocalMap类型
return t.threadLocals;
}
接下来看一下如果map为空,走了createMap方法
这里新建了一个ThreadLocalMap来赋值给线程t里面的值,也就是上面的getMap中t.threadLocals
ThreadMap往下走,看到一个构造函数,里面是初始化一些变量,
INITIAL_CAPACITY 数组默认值16
int i 这个是通过hash找到桶的下标
size 桶的数量,这里设置为1
setThreshold是当前桶到达数量多少时的扩容阈值 是桶长度的2/3,16*2/3=10,也就是到达10的时候就要扩容了
分析完了创建,接下来看一下如果存在map具体逻辑是怎么做的
这里我们新建多一个,set值进去
很明显已经进入到了map.set()方法了,往下继续走
tab拿到整个桶,len桶长度,i是hash桶下标
for循环里面,先判断桶下标是否是null,如果不为null,就进入循环,很明显我们这次的桶是null的,如果不想为null,可以设置同一个threadLocal的value
接下来我们往下走
这里发现在new 一个桶,放到我们数组hash得到的数组下,长度+1,重点来了,!cleanSomeSlots(i, sz) && sz >= threshold,这是判断长度是否达到阈值,初始时的阈值是10,如果大于就扩容,那有小伙伴就问了,这个cleanSomeSlots是什么来的,别急,我们继续往下走
这里的i实际是桶的下标位置,从while中是通过n来进行条件判断的说明n就是用来控制扫描趟数(循环次数),这里先判断桶下标的下一个位置是否有值,如果有值,不为null,并且get()出来的值为null,那就说明是脏的entry桶,这个时候进入清除阶段。
这里为什么要做这种判断呢?
这是为了避免发生内存泄漏,防止线程迟迟没结束,根可达分析,gc迟迟未回收,这里的n如果检测到了脏enty之后,就把n=len,主要是扩大扫描的次数,这样扫描的范围也会增大,从而检测到更多的脏entry。
接下来我们来看看expungeStaleEntry是如果工作的?
设置脏桶的value为null,和桶数组null,长度减1,清除完了当前的脏痛之后,继续往下扫描,直到tab[i]==null的时候停止,
为什么这个时候停止呢?
原因是脏桶的定义是key为null,桶不为null才算脏桶,所以当为null的时候就退出了
如果往下查找过程中又发现k==null,也就是脏桶,这个时候跟上面一样清除
那下面的else是干嘛的呢?
这里其实是处理rehash情况下,如果这个时候发生rehash的情况,往下找不为null的,如果找到了,放上去。
至此,整个set正常流程已经结束了,那还有非正常流程吗?有的,刚才我们看到
这里其实做了两件事,第一,向前查找脏桶,第二向后查找脏桶
1.向前找到脏桶
向后查找发现后有key可以替换,覆盖i桶位置,并且交换i和当前脏桶位置,cleanSomeSlots位置到前脏桶位置上
向后没找到可以替换的,插入到当前脏桶位置,cleanSomeSlots位置到前脏桶位置上
2.向前没有脏桶
向后查找发现脏桶,并且后有key可以替换,覆盖i桶位置,并且交换i和当前脏桶位置,cleanSomeSlots放到后查询脏桶位置
向后没找到可以替换的,插入到当前脏桶位置,cleanSomeSlots位置到前脏桶位置上
至此整个流程结束