专栏名称: 蚂蚁金服ProtoTeam
数据前端团队
目录
相关文章推荐
51好读  ›  专栏  ›  蚂蚁金服ProtoTeam

ThreadLocal 源码分析

蚂蚁金服ProtoTeam  · 掘金  · 前端  · 2017-12-07 07:34

正文

请到「今天看啥」查看全文


代码1.3

        private void set(ThreadLocal key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);//有没有很熟悉

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {//线性探测法
                ThreadLocal k = e.get();

                if (k == key) {//key相同 替换值
                    e.value = value;
                    return;
                }
                 //Entry 集成自 WeakReference<ThreadLocal>  k很有可能为null
                if (k == null) {
                    replaceStaleEntry(key, value, i);//查看代码1.4
                    return;
                }
            }

            tab[i] = new Entry(key, value);//表里没数据  生成节点加入进去
            int sz = ++size;//更改当前size
            if (!cleanSomeSlots(i, sz) && sz >= threshold)//判断是否触发阈值 触发则扩容
                rehash();//查看代码1.7
        }

主要用线性探测法向数组中确定节点位置,与HashMap的链地址法实现方式不一样。

代码1.4
   //replaceStaleEntry方法  
        private void replaceStaleEntry(ThreadLocal key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            int slotToExpunge = staleSlot;//记录删除节点位置起始位置 
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))//从数组往前找 有节点但节点无key值则更新slotToExpunge ,否则停止查找
                if (e.get() == null)
                    slotToExpunge = i;

            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {//线性探索查找key相同节点
                ThreadLocal k = e.get();

                if (k == key) {//如果 k == key 则更新value 讲该节点更新到 staleSlot位置上 
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                   //清除部分节点expungeStaleEntry()查看代码1.5 cleanSomeSlots()查看代码1.6
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);//未找到 生成新节点放入

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
代码1.5 方法expungeStaleEntry()
       // 从删除节点到后边遍历 到第一个为 null节点之间的节点都经过检测 返回第一个null节点位置
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;//删除该位置节点
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {//从当前节点现象探索遍历
                ThreadLocal k = e.get();
                if (k == null) {//删除 key为null节点 
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);//根据数组长度计算出新的位置 因为数组有可能扩容
                    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位置节点进行线性探测法确定位置
                            h = nextIndex(h, len);
                        tab[h] = e;//讲e节点更新到h位置
                    }
                }
            }
            return i;
        }
代码 1.6 方法cleanSomeSlots() 用于清除线性探索方向上的空节点
        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);// 见 2.2.2 
                }
            } while ( (n >>>= 1) != 0);//n = n>>>1 无符号右移动并赋值 这边每次除以2有点不太理解 欢迎大家讨论
            return removed;
        }
代码 1.7
        private void rehash() {
            expungeStaleEntries(); //见下边

            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();//见1.8
        }

        /**
         * Expunge all stale entries in the table.
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);//见1.5
            }
        }
    }
代码1.8 resize()
        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;//扩容 容量依然是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) {
                        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;
        }

上边就是ThreadLocal中set()方法的实现,主要: 向数组中插入节点,根据key (ThreadLocal)的threadLocalHashCode&(len-1)决定位置,然后根据线性探索法解决冲突问题,包括如果数组size超过阈值则扩容。







请到「今天看啥」查看全文