ThreadLocal -- 01 -- ThreadLocal内部类ThreadLocalMap源码解析

原文链接:ThreadLocal – 01 – ThreadLocal内部类ThreadLocalMap源码解析


相关文章:


ThreadLocalMap 是 ThreadLocal 的内部类,是一个自定义的哈希映射,用于维护线程的局部变量;其方法的访问权限都是私有的,因此只有 ThreadLocal 可以调用 ThreadLocalMap 中的方法,而其他类则不可以;此外其类访问权限是包级别的,也就是同一个包下面的类可以访问 ThreadLocalMap,但是不能调用 ThreadLocalMap 中的方法


一、数据结构

  • 在 Thread 类中有一个类型为 ThreadLocal.ThreadLocalMap 的变量 threadLocals,也就是说,每一个线程都有一个自己的 ThreadLocalMap

  • 每个线程在往 ThreadLocal 中存放值时,其实就是在往自己的 ThreadLocalMap 中存放值,读取也是从自己的 ThreadLocalMap 中读取值,从而实现了线程隔离

  • ThreadLocalMap 的结构类似于 HashMap,不过 HashMap 是由数组 + 链表 + 红黑树(JDK8) 来实现的,而 ThreadLocalMap 是由数组来实现的


二、内部类解析

  • Entry

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            // 以当前 ThreadLocal 作为 key
            super(k);
            // 以与当前 ThreadLocal 关联的值作为 value
            value = v;
        }
    }
    
    • Entry 是 ThreadLocalMap 的内部类,其继承自 WeakReference,是一个弱引用

    • entry.get() 为 null 时,表示不再引用其关联的 ThreadLocal 对象,此时可以从 ThreadLocalMap 中将该 Entry 对象删除

    • 弱引用

      • 用于关联非必需对象,被弱引用关联的对象只能生存到下一次垃圾回收之前

      • 当 GC 工作时,无论当前内存是否足够,能会回收掉只被弱引用关联的对象


三、字段解析

  • INITIAL_CAPACITY

    private static final int INITIAL_CAPACITY = 16;
    
    • ThreadLocalMap 初始容量,默认为 16,且必须为 2 的幂次方
  • table

    private Entry[] table;
    
    • 哈希桶数组,长度必须为 2 的幂次方
  • size

    private int size = 0;
    
    • 哈希桶数组容量
  • threshold

    private int threshold;
    
    • 扩容阈值

四、构造方法解析

  • ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        // 初始化哈希桶数组
        table = new Entry[INITIAL_CAPACITY];
        // 计算 firstKey 对应的哈希桶数组下标
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        // 将 Entry 对象存放至哈希桶数组对应下标处
        table[i] = new Entry(firstKey, firstValue);
        // 设置哈希桶数组容量
        size = 1;
        // 设置扩容阈值
        setThreshold(INITIAL_CAPACITY);
    }
    
    • 使用指定的 key 和 value 来构建 ThreadLocalMap

    • 这里需要特别说明的是,明面上我们是以 ThreadLocal 作为 key,以 ThreadLocal 关联的值作为 value 来实例化 Entry 对象,但由于 Entry 是一个弱引用对象,因此实际上我们是以 ThreadLocal 的弱引用对象作为 key,以 ThreadLocal 关联的值作为 value 来实例化 Entry 对象

  • ThreadLocalMap(ThreadLocalMap parentMap)

    private ThreadLocalMap(ThreadLocalMap parentMap) {
        // 获取临时哈希桶数组
        Entry[] parentTable = parentMap.table;
        // 获取临时哈希桶数组长度
        int len = parentTable.length;
        // 设置扩容阈值
        setThreshold(len);
        // 初始化哈希桶数组
        table = new Entry[len];
    
        /*
         * 循环临时哈希桶数组,
         * 将元素存放至新的哈希桶数组中
         */
        for (int j = 0; j < len; j++) {
            // 获取 Entry 对象
            Entry e = parentTable[j];
            // 如果 Entry 对象不为 null
            if (e != null) {
                @SuppressWarnings("unchecked")
                /*
                 * 获取弱引用关联的 ThreadLocal 对象
                 * (我们可以简单地将其视为 ThreadLocalMap 的 key,实际上是其弱引用)
                 */
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                // 如果 key 不为 null
                if (key != null) {
                    /*
                     * 调用 InheritableThreadLocal 的
                     * childValue(T parentValue) 方法来获取 value
                     */
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    // 计算 key 对应的哈希桶数组下标
                    int h = key.threadLocalHashCode & (len - 1);
                    // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    // 将 Entry 对象存放至哈希桶数组新计算出来的下标处
                    table[h] = c;
                    size++;
                }
            }
        }
    }
    
    • 将另一个 ThreadLocalMap 中的数据拷贝一份到自身的存储结构中

    • 该构造方法仅由 createInheritedMap(ThreadLocalMap parentMap) 方法进行调用


五、方法解析

1、setThreshold(int len) 相关方法
  • setThreshold(int len)

    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    • 设置扩容阈值,负载系数为 2/3,即取原容量的 2/3 作为扩容阈值

2、nextIndex(int i, int len) 相关方法
  • nextIndex(int i, int len)

    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
    
    • 以参数 i 作为单位递增,计算下一个哈希桶数组下标

3、prevIndex(int i, int len) 相关方法
  • prevIndex(int i, int len)

    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
    
    • 以参数 i 作为单位递减,计算上一个哈希桶数组下标

4、getEntry(ThreadLocal<?> key) 相关方法
  • getEntry(ThreadLocal<?> key)

    private Entry getEntry(ThreadLocal<?> key) {
        // 计算 key 对应的哈希桶数组下标
        int i = key.threadLocalHashCode & (table.length - 1);
        // 获取计算得到的下标处的 Entry
        Entry e = table[i];
        /*
         * 如果 Entry 对象不为 null,且其 get() 方法
         * 返回值等于 key,则返回该 Entry 对象
         */
        if (e != null && e.get() == key)
            return e;
        /*
         * 如果不满足上述条件,则调用
         * getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) 
         * 方法来获取 Entry 对象
         */
        else
            return getEntryAfterMiss(key, i, e);
    }
    
    • 根据 key 获取 Entry 对象
  • getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)

    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
    
        // 如果 Entry 对象不为 null
        while (e != null) {
            // 获取弱引用关联的 ThreadLocal 对象
            ThreadLocal<?> k = e.get();
            /*
             * 如果获取到的 key 等于参数 key,
             * 则返回该 Entry 对象
             */
            if (k == key)
                return e;
            /*
             * 如果获取到的 key 为 null,则调用
             * expungeStaleEntry(int staleSlot) 
             * 方法来清除无用数据
             */
            if (k == null)
                expungeStaleEntry(i);
            // 如果获取到的 key 不等于参数 key,则计算下一个下标
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }
    
    • 当根据 key 获取不到 Entry 对象时,使用此方法来获取 Entry 对象
  • expungeStaleEntry(int staleSlot)

    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
    
        // 清除哈希桶数组指定下标处的数据
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;
    
        Entry e;
        int i;
        // 从 (当前下标加 1) 处开始向后循环
        for (i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            // 获取弱引用关联的 ThreadLocal 对象
            ThreadLocal<?> k = e.get();
            // 如果获取到的 k 为 null,则清除该数据
            if (k == null) {
                e.value = null;
                tab[i] = null;
                size--;
            // 如果获取到的 k 不为 null
            } else {
                // 重新进行哈希运算,计算 k 对应的哈希桶数组的新下标
                int h = k.threadLocalHashCode & (len - 1);
                // 如果新下标不等于旧下标
                if (h != i) {
                    // 清除旧下标处的数据
                    tab[i] = null;
                    // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    // 将 Entry 对象存放至哈希桶数组新计算出来的下标处
                    tab[h] = e;
                }
            }
        }
        return i;
    }
    
    • 先清除哈希桶数组指定下标处的数据,然后从 (指定下标加 1) 处开始向后循环

    • 循环过程中,清除过期数据;对于不过期的数据,则重新进行哈希运算,定位其在新数组中的位置,如果该位置上已经有数据存在,则将该数据存放至离此位置最近的 Entry = null 的桶中


5、set(ThreadLocal<?> key, Object value) 相关方法
  • set(ThreadLocal<?> key, Object value)

    private void set(ThreadLocal<?> key, Object value) {
    
        Entry[] tab = table;
        int len = tab.length;
        // 计算 key 对应的哈希桶数组下标
        int i = key.threadLocalHashCode & (len-1);
    
        // 从当前下标处开始向后循环
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            // 获取弱引用关联的 ThreadLocal 对象
            ThreadLocal<?> k = e.get();
    
            /*
             * 如果获取到的 k 等于参数 key,
             * 则对 value 进行赋值,并直接返回
             */
            if (k == key) {
                e.value = value;
                return;
            }
    
            /*
             * 如果获取到的 k 为 null,则调用 
             * replaceStaleEntry(ThreadLocal<?> key, 
             * Object value, int staleSlot) 方法来替换过期数据
             * 并直接返回
             */
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
    
        /*
         * 用 key 和 value 重新构建一个 Entry 对象,
         * 将其存放至哈希桶数组对应下标处
         */
        tab[i] = new Entry(key, value);
        int sz = ++size;
        /*
         * 调用 cleanSomeSlots(int i, int n) 方法,
         * 进行启发式清理,如果未清理任何数据,且
         * sz 大于等于扩容阈值,则调用 rehash() 方法,
         * 重新整理或扩大哈希桶数组
         */
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    
    • 设置与 key 关联的 value
  • replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot)

    private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                   int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;
        Entry e;
    
        /*
         * slotToExpunge 表示进行过期数据
         * 清理的起始下标,初始值为 staleSlot
         */
        int slotToExpunge = staleSlot;
        // 从 (当前下标减 1) 处开始向前循环
        for (int i = prevIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = prevIndex(i, len))
            // 如果弱引用关联的 ThreadLocal 对象为 null
            if (e.get() == null)
                // 则将 slotToExpunge 更新为当前下标 i
                slotToExpunge = i;
    
        // 从 (当前下标加 1) 处开始向后循环    
        for (int i = nextIndex(staleSlot, len);
             (e = tab[i]) != null;
             i = nextIndex(i, len)) {
            // 获取弱引用关联的 ThreadLocal 对象
            ThreadLocal<?> k = e.get();
    
            /*
             * 如果获取到的 k 等于参数 key
             */
            if (k == key) {
                // 对 value 进行赋值
                e.value = value;
    
                /*
                 * 交换当前 staleSlot 位置,
                 * 用于维护哈希桶数组顺序
                 */
                tab[i] = tab[staleSlot];
                tab[staleSlot] = e;
    
                /*
                 * 如果 slotToExpunge 等于 staleSlot 
                 * (说明之前的向前循环并没有找到过期数据),
                 * 则将当前下标 i 赋值给 slotToExpunge,
                 * 并从此下标开始向后循环清除过期数据
                 */
                if (slotToExpunge == staleSlot)
                    slotToExpunge = i;
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                return;
            }
    
            /*
             * 如果获取到的 k 为 null,
             * 且 slotToExpunge 等于 staleSlot
             * (说明之前的向前循环并没有找到过期数据),
             * 则将当前下标 i 赋值给 slotToExpunge
             */
            if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }
    
        /*
         * 如果找不到 key 对应的 value,
         * 则用 key 和 value 重新构建一个 Entry 对象,
         * 将其存放至哈希桶数组对应下标处
         */
        tab[staleSlot].value = null;
        tab[staleSlot] = new Entry(key, value);
    
        /*
         * 如果 slotToExpunge 不等于 staleSlot
         * (说明之前的向前循环找到了过期数据),
         * 则从 slotToExpunge 开始向后循环清除过期数据
         */
        if (slotToExpunge != staleSlot)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    }
    
    • 将过期数据替换成指定 key、value 的新数据
  • cleanSomeSlots(int i, int n)

    private boolean cleanSomeSlots(int i, int n) {
        // 标记是否清理过数据
        boolean removed = false;
        Entry[] tab = table;
        int len = tab.length;
        do {
            // 计算下一个下标
            i = nextIndex(i, len);
            // 获取计算得到的下标处的 Entry
            Entry e = tab[i];
            /*
             * 如果 Entry 对象不为 null,
             * 且其关联的 ThreadLocal 对象为 null
             * (说明该 Entry 对象已过期)
             */
            if (e != null && e.get() == null) {
                n = len;
                removed = true;
                // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除
                i = expungeStaleEntry(i);
            }
        } while ( (n >>>= 1) != 0);
        return removed;
    }
    
    • 启发式清理,对哈希桶数组长度进行减半循环,在循环中调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除

    • 如果清理过数据,则返回 true;如果没有清理过数据,则返回 false

  • rehash()

    private void rehash() {
        // 清除所有过期数据
        expungeStaleEntries();
    
        /*
         * 如果 size 大于等于 threshold - threshold / 4,
         * (即 size 大于等 threshold * 3/4) 则进行扩容
         */
        if (size >= threshold - threshold / 4)
            resize();
    }
    
    • 重新整理或扩大哈希桶数组,首先清理所有过期数据,再对当前哈希桶数组容量进行判断,如果大于等于当前扩容阈值的 3/4,则进行扩容
  • expungeStaleEntries()

    private void expungeStaleEntries() {
        Entry[] tab = table;
        int len = tab.length;
        // 从头开始遍历哈希桶数组
        for (int j = 0; j < len; j++) {
            Entry e = tab[j];
            /*
             * 如果 Entry 对象不为 null,
             * 且其关联的 ThreadLocal 对象为 null
             * (说明该 Entry 对象已过期)
             */
            if (e != null && e.get() == null)
                // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除
                expungeStaleEntry(j);
        }
    }
    
    • 清除哈希桶数组中所有过期数据
  • resize()

    private void resize() {
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        // 新哈希桶数组长度为原哈希桶数组的 2 倍
        int newLen = oldLen * 2;
        Entry[] newTab = new Entry[newLen];
        int count = 0;
    
        // 从头开始遍历原哈希桶数组
        for (int j = 0; j < oldLen; ++j) {
            // 获取 Entry 对象
            Entry e = oldTab[j];
            // 如果 Entry 对象不为 null
            if (e != null) {
                // 获取弱引用关联的 ThreadLocal 对象
                ThreadLocal<?> k = e.get();
                // 如果获取到的 k 为 null
                if (k == null) {
                    // 将 Entry 对象的 value 设置为 null,方便 GC 对其进行回收
                    e.value = null; // Help the GC
                // 如果获取到的 k 不为 null
                } else {
                    // 计算 k 对应的哈希桶数组下标
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 如果哈希桶数组对应下标处已经有元素存在,则计算下一个下标
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 将 Entry 对象存放至哈希桶数组新计算出来的下标处
                    newTab[h] = e;
                    count++;
                }
            }
        }
    
        // 设置扩容阈值
        setThreshold(newLen);
        size = count;
        table = newTab;
    }
    
    • 对哈希桶数组进行 2 倍扩容

6、remove(ThreadLocal<?> key) 相关方法
  • remove(ThreadLocal<?> key)

    private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
        // 计算 key 对应的哈希桶数组下标
        int i = key.threadLocalHashCode & (len-1);
        // 从当前下标处开始向后循环
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            /*
             * 如果关联的 ThreadLocal 对象
             * 等于参数 ThreadLocal 对象
             */
            if (e.get() == key) {
                e.clear();
                // 调用 expungeStaleEntry(int staleSlot) 方法对过期数据进行清除
                expungeStaleEntry(i);
                return;
            }
        }
    }
    
    • 删除 key 对应 Entry 对象
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值