• ThreadLocal源码第二讲(ThreadLocalMap)



    1、ThreadLocalMap 简介

    什么是弱引用?

    A a = new A();     								// 强引用
    WeakReference weakA = new WeakReference(a);  	// 弱引用
    a=null;
    
    • 1
    • 2
    • 3

    a是强引用,因为是强引用,后面那个new的A对象不会被GC 回收,但当a==null时,new的对象不会因为有weakA 而保留,依然会被GC回收,因为weakA 是一个弱引用
    在这里插入图片描述

    2、源码分析

    下面详细讲解一下 ThreadLocalMap 的源码

    成员属性

    ThreadLocalMap 内部类的属性

          /** 什么是弱引用?
             * A a = new A();  强引用
             * WeakReference weakA = new WeakReference(a); 弱引用
             *
             * a = null
             * 下次GC时 对象 a 就被回收了,
             *
             * key使用的是弱引用保留,key保留的是ThreadLocal对象
             * value 使用的是强引用,value保存的是ThreadLocal对象与当前线程相关联的value
             *
             * entry的 key使用弱引用有什么好处?
             * 当ThreadLocal对象失去强引用且对象GC 回收后,散列表中的 与ThreadLocal对象相关联的entry#key再次去key.get() 时,拿到的是null
             * 站在map角度就可以区分出哪些entry是过期的,哪些entry是非过期的。
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
            /**
             * 初始化当前map 内部散列表的初始长度
             */
            private static final int INITIAL_CAPACITY = 16;
    
            /**
             * The table, resized as necessary.
             * table.length MUST always be a power of two.
             * ThreadLocalMap 内部散列表数组引用,数组的长度 必须是2的次方
             */
            private Entry[] table;
    
            /**  当前散列表数组 占用情况,存放了多少entry
             */
            private int size = 0;
    
            /**
             * 扩容触发阈值 初始值:len * 2 / 3
             * 触发后调用rehash() 方法
             * rehash() 方法先做一个全局检查 过期数据,把散列表中所有过期的entry移除
             * 如果移除以后 当前 散列表中的entry数量任然到达 threshold * 3/4 就进行扩容
             */
            private int threshold; // Default to 0
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    一些小方法

    分别是setThreshold ,nextIndex ,prevIndex ,见名知意
    这些方法并不是主要的方法,而是主要方法中调用的一些小的操作方法,比较简单,所以就放在一起看。

         /**
             * 将阈值设置为当前数组长度的 2/3;
             */
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
    
            /**
             * 参数1:当前下标   参数2:当前散列表数组长度
             */
            private static int nextIndex(int i, int len) {
                //当前下标+1 小于 散列表数组的话,返回len+1
                //否则 下标+1 == 散列表数组的长度,返回0;
                //实际形成一个环绕式的访问
                return ((i + 1 < len) ? i + 1 : 0);
            }
    
            /**
             * 参数1:当前下标   参数2:当前散列表数组长度
             */
            private static int prevIndex(int i, int len) {
                //当前下标-1 大于等于 0,返回 i-1;
                //否则说明 下标-1 等于 -1,返回 散列表最大下标len-1
                //实际形成一个环绕式的访问
                return ((i - 1 >= 0) ? i - 1 : len - 1);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    构造方法

    实际上有两个构造方法,另一个是私有构造方法:private ThreadLocalMap(ThreadLocalMap parentMap)

           /**
             * 构造方法:
             * 因为 Thread.threadLocals 字段时延迟初始化的,只有线程第一次存储Entry(threadLocal-value) 时,才会创建threadLocalMap对象
             *
             * firstKey:threadLocal对象
             * firstValue:当前线程与threadLocal对象关联的value
             */
            ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                //创建entry 数组长度为16,表示threadLocalMap内部的散列表
                table = new Entry[INITIAL_CAPACITY];
                //寻址算法:key.threadLocalHashCode & (table.length - 1)
                //table.length 数组的长度一定是2的次方
                //table.length-1 有什么特征? 转化为2进制后都是1:  16 - 1 =》 1 0000 - 1 =》 1111
                //1111 与任何数组进行 & 运算后 得到的数值 一定是 小于等于 1111
    
                //i 计算出来的结果 一定是 小于等于 1111 的
                int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                //创建entry对象 存放到指定位置的slot中
                table[i] = new Entry(firstKey, firstValue);
                //设置size=1
                size = 1;
                //设置扩容阈值 16 * 2/3 =10
                setThreshold(INITIAL_CAPACITY);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    getEntry方法

    这个方法是 ThreadLocal.get()的主要实现方法,get的逻辑就是来源这个方法

        /**
             * @param  key the thread local object
             * @return the entry associated with key, or null if no such
             *
             * ThreadLocal对象 get() 操作实际上是有ThreadLocalMap.getEntry()代理完成的
             * key:某个ThreadLocal对象,因为散列表中存储的entry.key 类型四 ThreadLocal
             */
            private Entry getEntry(ThreadLocal<?> key) {
                //路由规则:ThreadLocal.threadLocalHashCode & (table.length - 1)=> index
                int i = key.threadLocalHashCode & (table.length - 1);
                //访问散列表中指定位置的slot
                Entry e = table[i];
                //条件一:成立,说明slot有值
                //条件二:成立,说明entry&key 与当前查询的key一致,返回当前entry 给上层就可以了
                //key是弱引用
                if (e != null && e.get() == key)
                    return e;
                else
                    //有几种情况到这里?
                    //1. e==null
                    //2.e.key != key
    
                //getEntryAfterMiss 方法 会继续向当前桶位后面继续搜索 e.key == key 的entry
                //因为存储时 发生hash冲突,并没有在entry层面形成链表,存储时的处理 就是线性的向后找到一个可以使用的slot,并存放进去
                    return getEntryAfterMiss(key, i, e);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    getEntryAfterMiss方法

           /**
             * @param  key the thread local object             ThreadLocal对象 就是 key
             * @param  i the table index for key's hash code    key算出来的index
             * @param  e the entry at table[i]                  table[index] 中的Entry
             * @return the entry associated with key, or null if no such
             */
            private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                //获取当前ThreadLocalMap 中的散列表table
                Entry[] tab = table;
                //获取table长度
                int len = tab.length;
    
                //条件:e!=null 说明向后查找的范围是有限的,碰到slot==null 的情况,搜索结束
                //e:循环处理的当前元素
                while (e != null) {
                    //获取当前slot中entry对象的key
                    ThreadLocal<?> k = e.get();
                    //条件成立:说明向后查询过程中找到合适的entry了,返回entry就ok了
                    if (k == key)
                        return e;
    
                    //条件成立:说明当前slot中的entry#key 关联的 ThreadLocal对象已经被GC回收了,因为key是弱引用,e.get() == null
                    if (k == null)
                        //做一次探测式过期数据回收
                        expungeStaleEntry(i);
                    else
                        //更新index,继续向后搜索
                        i = nextIndex(i, len);
                    //获取下一个slot中的entry
                    e = tab[i];
                }
                //执行到这里说明:关联区段内 都没有找到相关数据
                return null;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    expungeStaleEntry方法

    探测式清理过期数据:向后查找过期数据,碰到slot==null就返回它的下标

           /**
             * A a = new A()
             * WeakReference b = new WeakReference(a)
             * a=null
             * 此时 b也等于null
             *
             * 参数stateSlot  table[stateSlot] 就是一个过期数据,以这个位置开始 继续向后查找过期数据,知道碰到slot==null的情况结束
             */
            private int expungeStaleEntry(int staleSlot) {
                //获取散列表
                Entry[] tab = table;
                //获取散列表当前长度
                int len = tab.length;
    
                // expunge entry at staleSlot
                //help GC
                tab[staleSlot].value = null;
                //因为staleSlot位置的entry 是过去的 这里直接置为null
                tab[staleSlot] = null;
                //因为上面干掉一个元素,所以 -1;
                size--;
    
                // Rehash until we encounter null
                //e:表示当前遍历节点
                Entry e;
                //i:表示当前遍历的index
                int i;
    
                //for 循环从staleSlot+1 的位置开始搜索过期数据,知道碰到slot==null 结束
                for (i = nextIndex(staleSlot, len);
                     (e = tab[i]) != null;
                     i = nextIndex(i, len)) {
    
                    //进入到for循环里面 当前entry一定不为null
    
                    //获取当前遍历节点 entry 的 key
                    ThreadLocal<?> k = e.get();
                    //条件成立:说明k表示的ThreadLocal对象 已经被GC回收了 。。当前entry属于脏数据了
                    if (k == null) {
                        //help GC
                        e.value = null;
                        //脏数据对应的slot设置null
                        tab[i] = null;
                        //因为上面干掉一个元素,所以 -1;
                        size--;
                    } else {
                        //执行到这里,说明当前遍历的slot中对应的entry是非过期数据
                        //因为前面有可能清理掉了几个过期数据
                        //且当前entry存储时有可能碰到hash冲突,往后偏移存储了,这个时候 应该去优化位置,让这个位置更可靠近 正确位置
                        //这样的话,查询的时候 效率才会更高
    
    
                        //重新计算当前entry 对应的index
                        int h = k.threadLocalHashCode & (len - 1);
    
                        //条件成立:说明当前entry存储时 就是发生hash冲突,然后向后偏移过了
                        if (h != i) {
                            //将entry 当前位置 设置为 null
                            tab[i] = null;
    
                            // Unlike Knuth 6.4 Algorithm R, we must scan until
                            // null because multiple entries could have been stale.
    
                            //h 是正确位置
    
                            //以正确位置h 开始,向后查找第一个可以存放entry的位置
                            while (tab[h] != null)
                                h = nextIndex(h, len);
                            //将当前元素放入到 举例正确位置更近的位置(有可能就是正确位置)
                            tab[h] = e;
                        }
                    }
                }
                return i;
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    在这里插入图片描述

    set方法

    ThreadLocal.set() 方法的 主要实现就是通过ThreadLocalMap.set()来实现的。

         /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be set
             *
             * ThreadLocal 使用set方法 给当前线程添加 ThreadLocal-value 键值对
             */
            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;
                //计算当前key 在散列表中的对应的位置
                int i = key.threadLocalHashCode & (len-1);
    
                //以当前key对应的slot位置 向后查询,找到可以使用的slot
                //什么slot可以使用呢?
                //1. k == key 说明是替换
                //2. 碰到一个过期的 slot,这个时候就可以强行占用
                //3. 查找过程中,碰到 slot == null了
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    //获取当前元素key
                    ThreadLocal<?> k = e.get();
    
                    //条件成立:说明当前set操作时替换操作
                    if (k == key) {
                        e.value = value;
                        return;
                    }
                    //条件成立:说明向下寻找过程中 碰到entry#key == null 的情况了,说明当前entry 是过期数据
                    if (k == null) {
                        //2. 碰到一个过期的 slot,这个时候就可以强行占用
                        //替换逻辑
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
                //执行到这里,说明for循环碰到了 slot==null的情况
    
                //在合适的slot中创建新的entry元素
                tab[i] = new Entry(key, value);
                //因为是新添加,size++
                int sz = ++size;
    
                //做一次启发式清理
                //条件一:!cleanSomeSlots(i, sz)成立:说明启发式清理工作,未清理到任何数据
                //条件二:sz >= threshold 成立:说明当前table内的entry已经达到了扩容阈值了,会触发rehash()
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    replaceStaleEntry方法

    在set()方法向下寻找可用桶位的过程中,如果碰到Entry.key == null (弱引用) 的情况,说明当前entry是过期数据,这个时候可以强行占用该桶位,通过replaceStaleEntry方法执行替换过期数据的逻辑:

           /**
             * @param  key the key
             * @param  value the value to be associated with key
             * @param  staleSlot index of the first stale entry encountered while
             *         searching for key.
             *
             *  替换过期的slot
             *
             *   key:键 ThreadLocal对象
             *   value:val
             *   staleSlot:上层方法 set方法,迭代查找时,发现的当前这个slot是一个过期的entry
             */
            private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                           int staleSlot) {
                //获取散列表
                Entry[] tab = table;
                //获取散列表长度
                int len = tab.length;
                //临时变量
                Entry e;
    
                // Back up to check for prior stale entry in current run.
                // We clean out whole runs at a time to avoid continual
                // incremental rehashing due to garbage collector freeing
                // up refs in bunches (i.e., whenever the collector runs).
    
                //表示 开始探测式清理过期数据的 开始下标,默认从当前stateSlot开始
                int slotToExpunge = staleSlot;
    
                //以当前stateSlot开始 向前迭代查找,默认从当前 stateSlot开始,for循环一致碰到null结束
                for (int i = prevIndex(staleSlot, len);
                     (e = tab[i]) != null;
                     i = prevIndex(i, len))
                    //条件成立:说明向前找到了过期数据,更新 探测清理过期数据的开始下标为 i
                    if (e.get() == null)
                        slotToExpunge = i;
    
                // Find either the key or trailing null slot of run, whichever
                // occurs first
    
                //以当前 stateSlot 向后去查找,知道碰到null为止
                for (int i = nextIndex(staleSlot, len);
                     (e = tab[i]) != null;
                     i = nextIndex(i, len)) {
                    //获取当前元素key
                    ThreadLocal<?> k = e.get();
    
                    // If we find key, then we need to swap it
                    // with the stale entry to maintain hash table order.
                    // The newly stale slot, or any other stale slot
                    // encountered above it, can then be sent to expungeStaleEntry
                    // to remove or rehash all of the other entries in run.
    
                    //条件成立:说明咱们是一个替换逻辑
                    if (k == key) {
                        //替换新数据
                        e.value = value;
    
                        //交换位置的逻辑
                        //将 table[stateSlot] 这个过期数据放到 当前循环到的table[i] 这个位置
                        tab[i] = tab[staleSlot];
                        //将tab[stateSlot] 中保存为当前entry,这样的话,咱们这个数据位置就被优化了
                        tab[staleSlot] = e;
    
                        // Start expunge at preceding stale entry if it exists
                        //条件成立:
                        //  1.说明 replaceStaleEntry一开始时的向前查找过期数据 并未找到过期的entry
                        //  2.向后检查过程中也未发现过期数据(向后查询是下面那个if做个事情)
                        if (slotToExpunge == staleSlot)
                            //开始探测式清理过期 数据的下标 修改为 当前循环的index
                            slotToExpunge = i;
    
                        //cleanSomeSlots:启发式清理
                        //参数: 探测式 结束的位置(entry==null的位置)数组长度
                        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.
                    //条件1: k==null 成立:说明当前遍历的entry 是一个过期数据..
                    //条件2:slotToExpunge == staleSlot成立:一开始时的向前查找过期数据 并未找到过期的entry
                    if (k == null && slotToExpunge == staleSlot)
                        //因为向后查询过程中查找到一个过期数据了,更新slotToExpunge 为当前位置
                        //前提条件:前驱扫描时 未发现过期数据
                        slotToExpunge = i;
                }
    
                //什么时候执行到这里?
                //向后查找过程中 并未发现 k==key 的entry,说明当前set操作 是一个添加逻辑
    
                // If key not found, put new entry in stale slot
                //直接将新数据添加到 tab[staleSlot] 对应的slot中
                tab[staleSlot].value = null;
                tab[staleSlot] = new Entry(key, value);
    
                // If there are any other stale entries in run, expunge them
                //条件成立:除了当前staleSlot以外,还未发现其它的过期slot了,所以要开启清理数据的逻辑
                if (slotToExpunge != staleSlot)
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    在这里插入图片描述

    cleanSomeSlots方法

    启发式清理过期节点:
    启发式清理工作的开始位置就是 :探测式 结束的位置(entry==null的位置)

          /**
             * 参数1:启发式清理工作的开始位置,探测式 结束的位置(entry==null的位置)
             * 参数2:一般是数组长度table.length,这里n表示结束条件
             */
            private boolean cleanSomeSlots(int i, int n) {
                //表示启发式清理工作 是否清理过过期数据
                boolean removed = false;
                Entry[] tab = table;
                //散列表长度
                int len = tab.length;
    
                do {
    
                    //为什么这里是从i+1 开始呢?
                    //cleanSomeSlots(i = expungeStaleEntry(slotToExpunge), len);
                    //因为 i = expungeStaleEntry(slotToExpunge) 返回值一定是null的下标
    
                    //获取当前i 的下一个下标
                    i = nextIndex(i, len);
                    Entry e = tab[i];
                    //条件一:e != null成立
                    //条件二:e.get() == null 成立,说明当前slot中保存的entry 是一个过期的数据
                    if (e != null && e.get() == null) {
                        //重新更新n 为table数组长度
                        n = len;
                        //表示清理过数据
                        removed = true;
                        //以当前过期的slot 为开始节点 做一次 探测式清理工作
                        i = expungeStaleEntry(i);
                    }
                    //探测5次
                    //假设table 长度为16
                    //16 >>> 1 ==> 8
                    //8 >>> 1 ==> 4
                    //4 >>> 1 ==> 2
                    //2 >>> 1 ==> 1
                    //1 >>> 1 ==> 0
                } while ( (n >>>= 1) != 0);
                return removed;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    在这里插入图片描述

    rehash方法

       private void rehash() {
                //这个方法执行完后,所有散列表内的所有过期的数据都会被干掉
                expungeStaleEntries();
    
                // Use lower threshold for doubling to avoid hysteresis
                //条件成立:说明清理完过期数据后,当前散列表内的entry数量 任然达到了 阈值的3/4,真正触发扩容
                if (size >= threshold - threshold / 4)
                    //真正扩容
                    resize();
            }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    resize方法

    真正扩容的方法:

       private void resize() {
                //获取当前散列表
                Entry[] oldTab = table;
                //获取当前散列表长度
                int oldLen = oldTab.length;
                //计算新表长度
                int newLen = oldLen * 2;
                //创建一个新的table散列表
                Entry[] newTab = new Entry[newLen];
                //表示新表table 中entry的数量
                int count = 0;
    
                //遍历老表 迁移数据到新表
                for (int j = 0; j < oldLen; ++j) {
                    //访问老表的指定位置的slot
                    Entry e = oldTab[j];
                    //条件成立:说明老表中的指定位置有数据
                    if (e != null) {
                        //获取entry#key
                        ThreadLocal<?> k = e.get();
                        //条件成立:说明老表中的当前位置的entry是一个过期数据,help GC,不用 在迁移
                        if (k == null) {
                            e.value = null; // Help the GC
                        } else {
                            //执行到这里说明老表中的当前位置是非过期数据,需要迁移到扩容后的新表
    
                            //计算出当前entry在扩容后的新表的存储位置
                            int h = k.threadLocalHashCode & (newLen - 1);
                            //while循环 就是拿到一个距离h最近的一个可以使用的slot
                            while (newTab[h] != null)
                                h = nextIndex(h, newLen);
                            //将数据存放到新表的合适的slot中
                            newTab[h] = e;
                            //数量+1
                            count++;
                        }
                    }
                }
    
                //设置下一次触发扩容的标准
                setThreshold(newLen);
                size = count;
                //将扩容后的新表的引用保存到 threadLocalMap 对象的table这里
                table = newTab;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    remove方法

            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;
                    }
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
  • 相关阅读:
    jvm丨class的类加载机制
    同一公司开发的多个软件,可以用同一张代码签名证书吗?
    使用Wireshark软件抓包(分析报文)
    easypoi多级表头、多个sheet导出,动态导出列
    一文教你如何使用Node进程管理工具-pm2
    carsim 2020 安装说明及多个版本冲突问题解决
    操作系统之微内核架构
    C高级Linux指令和shell脚本
    Python自动合并Word文件同时添加分页符的方法
    491. 递增子序列
  • 原文地址:https://blog.csdn.net/m0_51809035/article/details/127676104