本文共 5530 字,大约阅读时间需要 18 分钟。
ThreadLocalthreadLocal = new ThreadLocal<>(); threadLocal.set(1);
1,threadLocal这种存储方式,内部只能存放一个引用(引用可以指向容器,比如map,或者integer这种单个的)
2,threadLocal的内部实现,是通过获取当前线程,再从当前线程中得到线程上存储的线程map ThreadLocalMap1,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,可以被复写转载地址:http://ttalf.baihongyu.com/