ThreadLocalMap源码解析

本文详细解析了ThreadLocalMap的内部工作原理,包括其构造方法、普通方法的作用及其实现细节,阐述了如何存储线程所有ThreadLocal与Value对应关系,以及如何处理过时的Entry。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

作用

ThreadLocalMap作用是存储线程所有ThreadLocal与Value对应关系。
其中ThreadLocal作为Key,使用的是弱引用,如果程序中没有对这个ThreadLocal的引用,ThreadLocal会被GC,但是Entry依然存在,只有在特定动作下,这些过时的Entry才会被清除。

构造方法

  • ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
    作用:构造一个以firstKey与firstValue为初始值的ThreadLocalMap。
  • private ThreadLocalMap(ThreadLocalMap parentMap)
    作用:通过把父ThreadLocalMap中的值复制过来,初始化一个ThreadLocalMap。

普通方法

  • private Entry getEntry(ThreadLocal<?> key)
    作用:得到与Key关联的Entry,如果没有直接命中,则使用getEntryAfterMiss方法。
  • private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)
    作用:当在它最近的哈希位置找不到Entry的时候,getEntry就会使用此方法,不断的向后查找 Entry,当发现后一个Entry失效,则会清除它,直到Entry为空或命重返回。
  • private void set(ThreadLocal<?> key, Object value)
    作用:设置与ThreadLocal相关的对象
  • private void remove(ThreadLocal<?> key)
    作用:通过Key去移除Entry
  • private void replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot)
    作用:替换与ThreadLocal相关的对象到过时位置中去。
  • private int expungeStaleEntry(int staleSlot)
    作用:从staleSlot位置开始,清除过时的Entry,重新排序有效的Entry,直到遇到Entry为null。
    返回:Entry为null时候,在table中的坐标。
  • private boolean cleanSomeSlots(int i, int n)
    作用:从i位置开始,最少运行log2(n)次,清理table中过期的Entry。
    返回:如果有清理过Entry返回True,没有返回False。

注意:ThreadLocalMap在Thread中被声明,并且只在ThreadLocal中被调用。

以下又是枯燥的源码


    /**
     * ThreadLocalMap是一个自定义的散列映射仅适用于维护线程本地的值。
     * 在ThreadLocal类之外不做任何动作。
     * 该类是包私有的,允许Thread类中声明此域。
     * 为了解决很大且长时间存留的使用,该散列表的Entry对key使用了弱引用。
     * 然而,因为没有引用队列,当表空间不足的时候才会移除过时的Entry。
     */
    static class ThreadLocalMap {

        /**
         * 这个散列映射里的Entry扩展了WeakReference,使用它的主引用域作为key
         * (这里是ThreadLocal对象)。注意,空Keys(例如:Entry.get()==null)
         * 意味着这个Key不再被引用,所以这个Entry会从table中删除。
         * 这些Entries在下面的代码中被称为“过时Entry”。
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** 与ThreadLocal关联的值 */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * 初始大小 -- 一定是二的幂。
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * 在必要的时候,需要调整这个表的长度。
		 * 长度必须是二的幂。
         */
        private Entry[] table;

        /**
         * 在这个Table里面,Entry的数量。
         */
        private int size = 0;

        /**
         * 当达到这个值的时候,需要调整长度。
         */
        private int threshold; // 默认是0

        /**
         * 设置阈值,当达到这个阈值的时候需要调整长度。
		 * 最坏的情况下,保留三分之二的负载系数。
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * 增量i根据len取模。
         */
        private static int nextIndex(int i, int len) {
			//i+1小于len吗?
			//true:返回i+1
			//false:返回0
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
			//i-1大于等于0吗?
			//true:返回i-1
			//false:返回len-1
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 构建一个新的初始化Map,包含firstKey与firstValue。
         * ThreadLocalMaps是懒加载创建的,所以当我们至少有一个Entry需要放到里面去的时候才创建。
         * ,所以当我们至少有一个Entry需要放到里面去的时候才创建。
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
			//初始化一个大小为INITIAL_CAPACITY的Entry数组
            table = new Entry[INITIAL_CAPACITY];
			//使用计算出来的i作为Entry的index
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
			//把Entry放入table中,index为i。
            table[i] = new Entry(firstKey, firstValue);
			//设置table大小为1
            size = 1;
			//设置重新调整大小的阈值
            setThreshold(INITIAL_CAPACITY);
        }

        /**
		 * 构建一个新的ThreadLocalMap,其中包含所有父类ThreadLocalMap中有效的Entry。
         * 仅由创建父类ThreadLocalMap调用。
         *
		 * @param parentMap 这个Map与父线程关联
         */
        private ThreadLocalMap(ThreadLocalMap parentMap) {
			//先把父类的table拿出来
            Entry[] parentTable = parentMap.table;
			//获取父类Table的长度
            int len = parentTable.length;
			//以父类Table的长度设置阈值
            setThreshold(len);
			//以父类Table的长度创建一个新的Table
            table = new Entry[len];

			//迭代父类Table
            for (int j = 0; j < len; j++) {
				//根据index获取父类table里的Entry
                Entry e = parentTable[j];
                if (e != null) {
					//获取Key,即ThreadLocal。
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
						//获取这个ThreadLocal对应的Value
                        Object value = key.childValue(e.value);
						//创建一个新的Entry,key与value与父类的Entry相同。
                        Entry c = new Entry(key, value);
						//计算ThreadLocal的哈希值
                        int h = key.threadLocalHashCode & (len - 1);
						//当这个哈希值在Table已经存在的时候,计算下一个Hash值。
                        while (table[h] != null)
                            h = nextIndex(h, len);
						//新的Entry存入Table中
                        table[h] = c;
						//size增加1
                        size++;
                    }
                }
            }
        }

		/**
         * 得到与Key关联的Entry。这个方法本身只处理快速路径:
         * 现有Key的直接命中。否则它将转发给getEntryAfterMiss。
         * 这是被设计来发挥直接命中的最大性能,在某种程度上
         * 通过这个方法更容易实现。
         *
         * @param  key ThreadLocal对象
         * @return entry 与Key关联的Entry。如果没有,就会返回null。
         */
        private Entry getEntry(ThreadLocal<?> key) {
			//计算index
            int i = key.threadLocalHashCode & (table.length - 1);
			//从table中拿出Entry
            Entry e = table[i];
			//如果Entry不为空,并且Entry的Key与param中的key相等。
            if (e != null && e.get() == key)
				//则返回这个Entry
                return e;
            else
                return getEntryAfterMiss(key, i, e);//如果以上为False,则会调用此方法。
        }

        /**
		 * 当在它最近的哈希位置找不到Entry的时候,getEntry就会使用此方法。
         *
         * @param  key ThreadLocal对象
         * @param  i the table index for key's hash code
         * @param  e the entry at table[i]
         * @return entry 与Key关联的Entry。如果没有,就会返回null。
         */
        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
			//把table赋值到tab
            Entry[] tab = table;
			//获取tab的长度
            int len = tab.length;

			//当Entry不等于空的时候
            while (e != null) {
				//把Entry的Key拿出来
                ThreadLocal<?> k = e.get();
				//如果Entry的Key等于参数中的key
                if (k == key)
					//直接返回Entry
                    return e;
				//如果Entry中的Key为null
                if (k == null)
					//清除过时Entry
                    expungeStaleEntry(i);
                else
					//计算下一个index
                    i = nextIndex(i, len);
                e = tab[i];
            }
			//最终返回null
            return null;
        }

        /**
         * 设置与ThreadLocal相关的对象
		 * 首先根据参数中的key,计算出在table中的hash坐标。
		 * 如果table的这个哈希坐标已经存在Entry了,就会有以下两种情况:
		 * 1、如果存在的Entry的Key等于参数中的key,直接把存在的Entry中的value替换成参数中的value,方法结束。
		 * 2、如果存在的Entry为过时的Entry,则会调用replaceStaleEntry方法,替换过时的Entry,方法结束。
		 * 如果table的这个hash坐标没有Entry,直接实例化一个新的Entry放进去。
		 * 最后去判断一下是否需要重新排列扩容一下table。
		 * 
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {
			
			//我们没有使用像get()方法那样的快速路径,因为至少那是相同的,
			//当使用set()去创建一个新的Entry与替换一个已经存在的Entry,
			//在这种状况下,快速路径将会比不是快速路径更容易失败。

			//把table赋值给tab
            Entry[] tab = table;
			//获取tab的长度
            int len = tab.length;
			//获取ThreadLocal的哈希坐标
            int i = key.threadLocalHashCode & (len-1);

			//首先从tab中拿出index为i的Entry,把值赋给e。
			//如果e不等于空,则继续,直到e等于空。
			//运行完一次之后,计算下一个i,并再次把tab中index为i的值赋给e
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
				//获取e的key
                ThreadLocal<?> k = e.get();
				//如果e的key与入参的key相同
                if (k == key) {
					//替换e的value
                    e.value = value;
					//方法结束
                    return;
                }

				//如果e中的key等于空,说明这个Entry已经失效了
                if (k == null) {
					//把原本的Entry替换成新的Entry
                    replaceStaleEntry(key, value, i);
					//方法结束
                    return;
                }
            }

			//当发现table中,index为i的位置没有Entry
			//就会把这个值设置在这里
            tab[i] = new Entry(key, value);
			//size自增
            int sz = ++size;
			//如果table中已经不存在过时的Entry了,并且Entry的个数已经大于等于阈值
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();//进行扩容并且重新哈希排序所有Entry
        }

        /**
         * 通过Key去移除Entry
         */
        private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
		 
        /**
		 * 将一个在一套操作中遇到的过时Entry替换为拥有特殊Key的Entry。
		 * 即使这个特殊Key的Entry已经存在,值参数中的value将会被储存进Entry。
         *
		 * 作为副作用,这个方法会清除“run”中所有过时Entry
		 * (“run”是两个空位置之间,一连串的Entry。)
         *
         * @param  key the key
         * @param  value 和这个Key关联的值
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
			
            int slotToExpunge = staleSlot;
			//不断地找前一个Entry,直到前一个Entry为空。
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)//如果前一个Entry失效了
                    slotToExpunge = i;//把slotToExpunge替换为前一个Entry的index

			//不断地找后一个Entry,直到后一个Entry为空。
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

				//如果参数中的key与后一个Entry中的key相等
				//表明拥有参数中key的Entry已经在table里面了
                if (k == key) {
					//现在失效的Entry要被已经存在的Entry替换
                    e.value = value;
                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

					//如果要被清除的位置与过时的位置相等
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;//过时的位置替换为后一个Entry的位置
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

				// 如果我们在反向扫描中没有发现过期条目,
				// 则在我们扫描key时发现的第一个过期Entry,就是第一个现在依然在“run”中的。
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

			// 如果key找不到,放入一个新的Entry在过时位置
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

			// 如果这里有任何其它过时的Entry在“run”中,清除他们。
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
		 
        /**
		 * 这个方法首先会把过时的位置清除了,然后会不断地计算出下一个index,
		 * 如果新的index拿到的Entry也是过时的,就会继续清除
		 * 如果新的index拿到的Entry还是有效的,则会看看index有没有因为扩容导致不正确了,如果不正确则重新计算搬移。
		 * 直到找到一个null,并且返回null在table中的index。
         *
         * @param staleSlot 已知具有空Key的位置索引
         * @return 在过时位置后,下一个空位置的索引。
         * (在过时位置与这个位置之间的所有值已经被检查是否删除)
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            //清除过时位置的Entry
            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)) {
				//获得Entry的Key。
                ThreadLocal<?> k = e.get();
				//如果Entry的Key为空,表明已经过时。
                if (k == null) {
					//同样清除这个位置的Entry
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
					//根据现在table的len重新哈希index
                    int h = k.threadLocalHashCode & (len - 1);
					//如果旧的index与新的index不一样
                    if (h != i) {
						//把旧的index存在的Entry清空
                        tab[i] = null;

                        //不像Knuth 6.4 Algorithm R中说的,
						//我们一定要扫描直到空值,因为多个条目可能已经过时。
                        while (tab[h] != null)//当新的位置已经有Entry了
							//计算下一个index
                            h = nextIndex(h, len);
						//把旧的有效的Entry放到新的位置
                        tab[h] = e;
                    }
                }
            }
			//直到遇到null,返回index。
            return i;
        }
		 
		/**
         * 为了找出过期的Entry试探性地扫描table中的一些位置
		 * 当新的Element被添加,或者其它过期元素被清除的时候,这个方法会被调用。
		 * 它执行了一个对数次数的扫描,作为不扫描与和Element数量成比例扫描之间的平衡,
		 * 这会找到所有垃圾,但会导致一些插入使用了O(n)的时间
         *
         * @param i 一个已知的没有持有过期Entry的位置,将会从i之后开始扫描。
         *
         * @param n 扫描控制:log2(n)个单元将会被扫描,除非一个过期的Entry被找出来,
         * 			在这种状况下,log2(table.length)-1个额外的单元会被扫描。
		 * 			当这个方法在元素插入的时候被调用,这个参数n就是元素的个数,
		 * 			但是当从replaceStaleEntry方法进入的时候,就是table的长度。
		 *			(记住:所有这些是可以被或多或少的增加n来改变的,以此来替代仅仅使用直对数n,
		 * 			但是这个版本是简单的、快速的,并且看起来工作的挺好。)
         *
         * @return true 如果任何过时条目已经被清除了。
         */
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
			//运行log2(n)次,原来是这么玩。无符号右移一位。
			while ((n >>>= 1) != 0){
				//计算下一个index
				i = nextIndex(i, len);
				//获取table中下标为index的Entry
                Entry e = tab[i];
				//当发现是失效的Entry的时候。
                if (e != null && e.get() == null) {
					//把table的长度赋值给n
                    n = len;
					//改变removed标志
                    removed = true;
					//移除table中index为i的值,并且刷新从i开始,但不包括i的table
					//最终返回遇到null的index
                    i = expungeStaleEntry(i);
                }
			}
			//返回removed标志
            return removed;
        }

		/**
		 * 重新打包 和/或 调整table大小。
		 * 首先扫描整个table,移除失效的Entry。
		 * 如果这个不能充分地缩小table的大小,请将表的大小增加一倍。
         */
        private void rehash() {
			//移除所有失效的Entry,调整有效的Entry。
            expungeStaleEntries();

            //使用较低的阈值进行倍增,以避免迟滞。
            if (size >= threshold - threshold / 4)
                resize();
        }

        /**
         * 倍增table的容量
         */
        private void resize() {
			//旧的table
            Entry[] oldTab = table;
			//旧的长度
            int oldLen = oldTab.length;
			//新的长度为旧的长度两倍
            int newLen = oldLen * 2;
			//重新创建一个table,并且长度是原来的两倍。
            Entry[] newTab = new Entry[newLen];
            int count = 0;
			//迭代旧的table
            for (int j = 0; j < oldLen; ++j) {
				//获得旧table中的Entry
                Entry e = oldTab[j];
				//如果Entry不为空
                if (e != null) {
					//获取这个Entry的Key(ThreadLocal)
                    ThreadLocal<?> k = e.get();
					//如果这个Entry的Key为空,说明已经失效了。
                    if (k == null) {
						//去掉value的引用,帮助垃圾回收。
                        e.value = null;
                    } else {//如果这个Entry的key不为空,说明有效。
						//用新的table长度重新计算这个Entry的哈希值。
                        int h = k.threadLocalHashCode & (newLen - 1);
						//如果新的index在新的table中已经占用了
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);//重新计算下一个index
						//把这个有效的Entry放入新的table中
                        newTab[h] = e;
						//计数加1
                        count++;
                    }
                }
            }

			//使用新的长度重新计算阈值
            setThreshold(newLen);
			//以下两个就不说了。。。
            size = count;
            table = newTab;
        }

        /**
         * 移除table里面所有失效的Entry
         */
        private void expungeStaleEntries() {
            Entry[] tab = table;
            int len = tab.length;
			//迭代table中所有的Entry
            for (int j = 0; j < len; j++) {
                Entry e = tab[j];
				//发现失效,立刻移除。
                if (e != null && e.get() == null)
                    expungeStaleEntry(j);
            }
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值