ThreadLocal原理以及使用

对threadLocal的实现的阅读

使用方式以及理解Set部分,包含创建

 ThreadLocal threadLocal = new ThreadLocal<>();
   threadLocal.set(1);

1,threadLocal这种存储方式,内部只能存放一个引用(引用可以指向容器,比如map,或者integer这种单个的)
2,threadLocal的内部实现,是通过获取当前线程,再从当前线程中得到线程上存储的线程map ThreadLocalMap

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逻辑比较简单,不贴代码了
主要是,get的时候,如果map存在,就去map里找,找到就返回,在找的过程中,如果发现废弃槽点,就进行清理
如果找不到,就返回null
如果map不存在,就设置默认值null进map,并且返回默认值
initialValue的方法为protected,可以被复写

你可能感兴趣的:(java&javaweb)