一、以下是 ThreadLocalMap 中几个关键方法的源码和分析,这些方法用于实现 ThreadLocal 对象与其关联值的存储、获取、移除、替换等操作:


1. set(ThreadLocal<?> key, Object value):将指定 ThreadLocal 对象关联的值设置为给定的值。

private void set(ThreadLocal<?> key, Object value) {
    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) {
            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();
}

解释:该方法首先获取 ThreadLocalMap 的内部数组 table 和长度 len。然后,根据 ThreadLocal 对象的哈希码和数组长度计算出在数组中的索引位置 i

接下来,它使用循环遍历数组中从索引位置 i 开始的连续的 Entry 对象。在遍历过程中,它首先获取当前 Entry 对象关联的 ThreadLocal 对象,并与指定的 ThreadLocal 对象 key 进行比较。如果找到匹配的 ThreadLocal 对象,则将该 Entry 对象的值更新为给定的值 value,然后返回。

如果当前 Entry 对象关联的 ThreadLocal 对象为 null,表示该 Entry 对象已过期,需要替换为新的 Entry 对象。它调用 replaceStaleEntry(key, value, i) 方法来替换过期的 Entry 对象,并返回。

如果遍历完整个连续的 Entry 对象序列后仍未找到匹配的 ThreadLocal 对象,表示该 ThreadLocal 对象在 ThreadLocalMap 中不存在,需要创建一个新的 Entry 对象,并将其放入数组的索引位置 i 处。

最后,它增加 ThreadLocalMap 的大小计数器 size,并检查是否需要进行清理和重新哈希操作。如果没有清理掉一些过期的 Entry 对象,并且大小计数器达到了阈值 threshold,则调用 rehash() 方法进行重新哈希操作。

总之,set(ThreadLocal<?> key, Object value) 方法用于将指定 ThreadLocal 对象关联的值设置为给定的值,并在必要时进行相关的清理和重新哈希操作,以保持 ThreadLocalMap 的数据的一致性和有效性。


2. getEntry(ThreadLocal<?> key) :该方法用于根据给定的 ThreadLocal 对象 key 获取对应的 Entry 节点。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

解释:该方法首先计算给定 ThreadLocal 对象 key 的哈希码 key.threadLocalHashCode,然后使用位运算 & 将哈希码与 table 数组的长度减 1 进行按位与运算,得到槽位的索引 i

接下来,获取槽位 i 处的节点 e

如果节点 e 不为 null 且关联的 ThreadLocal 对象与给定的 key 相等(通过 e.get() == key 判断),表示找到了匹配的节点,直接返回该节点。

否则,调用 getEntryAfterMiss(key, i, e) 方法来处理未命中的情况,传入给定的 key、槽位索引 i 和节点 e

getEntryAfterMiss(key, i, e) 方法的具体实现在源码中没有给出,但可以推测它可能是用于处理哈希冲突的情况,例如在线性探测法中继续查找下一个槽位。

总之,getEntry(ThreadLocal<?> key) 方法用于根据给定的 ThreadLocal 对象 key 获取对应的 Entry 节点。它通过计算哈希码和位运算来确定槽位的索引,并在槽位上查找匹配的节点。如果未找到匹配的节点,则可能调用另一个方法来处理哈希冲突的情况。


3. remove(ThreadLocal<?> key):该方法用于从 ThreadLocalMap 中移除指定的 ThreadLocal 对象及其关联的值。

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;
        }
    }
}

解释:该方法首先获取 ThreadLocalMap 的内部数组 table 和长度 len。然后,根据 ThreadLocal 对象的哈希码和数组长度计算出在数组中的索引位置 i

接下来,它使用循环遍历数组中从索引位置 i 开始的连续的 Entry 对象。在遍历过程中,它首先获取当前 Entry 对象关联的 ThreadLocal 对象,并与指定的 ThreadLocal 对象 key 进行比较。如果找到匹配的 ThreadLocal 对象,则调用 clear() 方法清除该 Entry 对象,将其关联的值置为 null

然后,它调用 expungeStaleEntry(i) 方法来清除过期的 Entry 对象,并将数组中的空槽位进行清理,以保持 ThreadLocalMap 的数据的一致性和有效性。

总之,remove(ThreadLocal<?> key) 方法用于从 ThreadLocalMap 中移除指定的 ThreadLocal 对象及其关联的值,并进行必要的清理操作。


4. replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot):该方法用于替换指定 ThreadLocal 对象关联的值,并进行相关的清理和重新哈希操作。

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)) {
        if (e.get() == null)
            slotToExpunge = i;
    }

    for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;

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

            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);

}

解释:该方法的作用是替换指定 ThreadLocal 对象关联的值,并进行相关的清理和重新哈希操作。首先,它获取 ThreadLocalMap 的内部数组 table 和长度 len。然后,它遍历数组,从指定位置 staleSlot 的前一个位置开始向前遍历,查找是否存在先前的过期 Entry 对象。这是为了一次性清理整个连续的过期 Entry 对象,以避免由于垃圾回收器在某些情况下(例如每次运行垃圾收集器时)释放大量引用而导致的不断增量的重新哈希操作。

接下来,它从指定位置 staleSlot 的下一个位置开始向后遍历,查找要么是指定 ThreadLocal 对象 key 的位置,要么是连续的空槽的位置。如果找到了指定 ThreadLocal 对象 key,则将其关联的值更新为新值 value,然后将该 Entry 对象与原来的过期 Entry 对象交换位置,以保持哈希表中的顺序。如果在向后遍历时没有找到过期的 Entry 对象,则将第一个遇到的过期 Entry 对象的位置记为 slotToExpunge

如果找不到指定 ThreadLocal 对象 key,则将新的 Entry 对象放入过期位置 staleSlot,并将其关联的值设为 null。

最后,如果存在其他过期的 Entry 对象,则调用 expungeStaleEntry(slotToExpunge) 方法来清理或重新哈希该连续的过期 Entry 对象。cleanSomeSlots() 方法用于清理一些过期 Entry 对象,以减少连续过期 Entry 对象的数量。

总之,replaceStaleEntry() 方法用于替换过期的 ThreadLocal 对象关联的值,并进行相应的清理和重新哈希操作,以保持 ThreadLocalMap 的数据的一致性和有效性。


5. resize():该方法用于调整 ThreadLocalMap 的大小,以便容纳更多的 Entry 对象。

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 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;
}

解释:该方法首先获取当前 ThreadLocalMap 的内部数组 table 和长度 oldLen。然后,创建一个新的长度为原长度两倍的数组 newTab

接下来,它遍历旧数组 oldTab 中的每个位置。对于非空的 Entry 对象,它首先获取关联的 ThreadLocal 对象 k。如果 knull,表示该 Entry 对象已过期,将其关联的值置为 null,以帮助垃圾回收。

否则,它根据 k 的哈希码和新数组的长度计算出在新数组中的索引位置 h。如果该位置已经被占用,则使用 nextIndex(h, newLen) 方法找到下一个可用的位置。然后,将当前 Entry 对象放入新数组的索引位置 h 处。

在遍历过程中,还会记录有效的 Entry 对象的数量 count

完成遍历后,它调用 setThreshold(newLen) 方法设置新的阈值,更新大小计数器 sizecount,并将 table 指向新数组 newTab

总之,resize() 方法用于调整 ThreadLocalMap 的大小,创建一个新的数组,并将旧数组中的有效 Entry 对象重新分布到新数组中,以便容纳更多的 Entry 对象,并保持 ThreadLocalMap 的数据的一致性和有效性。


6. getEntry(ThreadLocal<?> key):该方法用于根据给定的 ThreadLocal 对象 keyThreadLocalMap 中查找对应的 Entry 对象。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

解释:该方法首先根据 key 的哈希码和 table 的长度计算出在数组中的索引位置 i。然后,获取数组中索引位置 i 处的 Entry 对象 e

如果 e 不为 null,并且 e 关联的 ThreadLocal 对象与给定的 key 相等(通过 e.get() == key 判断),则找到了匹配的 Entry 对象,直接返回。

否则,调用 getEntryAfterMiss(key, i, e) 方法进行进一步处理。该方法会在数组中的索引位置 i 处进行线性探测,直到找到匹配的 Entry 对象或者遇到 null

最终,如果找到了匹配的 Entry 对象,则返回该对象;否则,返回 null

总之,getEntry(ThreadLocal<?> key) 方法用于在 ThreadLocalMap 中查找给定 ThreadLocal 对象 key 对应的 Entry 对象。它通过哈希码和线性探测的方式在数组中进行查找,并返回匹配的 Entry 对象或者 null


7. set(ThreadLocal<?> key, Object value):该方法用于将给定的 ThreadLocal 对象 key 和对应的值 value 插入到 ThreadLocalMap 中。

private void set(ThreadLocal<?> key, Object value) {
    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) {
            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();
}

解释:该方法首先获取 table 数组的引用,并获取数组的长度 len。然后,根据 key 的哈希码和 len-1 进行按位与操作,计算出在数组中的索引位置 i

接下来,通过循环遍历链表或红黑树,查找是否存在与给定的 key 相等的 ThreadLocal 对象。在每次迭代中,首先获取当前节点 e 关联的 ThreadLocal 对象 k

如果 k 与给定的 key 相等(通过 k == key 判断),则更新节点 e 的值为给定的 value,然后返回。

如果 knull,表示当前节点已经被回收,调用 replaceStaleEntry(key, value, i) 方法替换当前节点的值,并返回。

如果以上两个条件都不满足,说明当前节点与给定的 key 不相等且不为 null,继续迭代到下一个节点。

如果遍历完链表或红黑树仍未找到匹配的节点,说明需要在索引位置 i 处插入新的节点。创建一个新的 Entry 对象,并将其放置在 tab[i] 处。

接着,增加 size 的计数,并检查是否需要进行清理操作(调用 cleanSomeSlots(i, sz) 方法),以及是否需要进行重新哈希操作(调用 rehash() 方法)。

总之,set(ThreadLocal<?> key, Object value) 方法用于将给定的 ThreadLocal 对象 key 和对应的值 value 插入到 ThreadLocalMap 中。它通过哈希码和线性探测的方式在数组中查找匹配的节点,并进行相应的操作。如果未找到匹配的节点,将创建新的节点并插入到数组中。



二、以下这三个方法用于回收ThreadLocalMap 中键为 null 的Entry对象的值(即为具体实例) 以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法。


1. expungeStaleEntry(int staleSlot) 方法:该方法用于清除指定位置 staleSlot 处的过期节点,并重新哈希其他节点,以保持哈希表的连续性。

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) {
            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 = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

解释:该方法首先获取 table 数组的引用,并获取数组的长度 len

接下来,将指定位置 staleSlot 处的过期节点的值设为 null,并将该位置的引用设为 null。然后,将 size 的计数减一。

接着,通过循环从 staleSlot 的下一个位置开始,遍历数组,处理可能的过期节点。在每次迭代中,获取当前节点 e 关联的 ThreadLocal 对象 k

如果 knull,表示当前节点已经被回收,将当前节点的值设为 null,并将该位置的引用设为 null。然后,将 size 的计数减一。

如果 k 不为 null,则计算 k 的哈希码,并将其与数组长度 len-1 进行按位与操作,得到新的索引位置 h

如果 h 不等于当前位置 i,说明节点需要重新哈希到新的位置。将当前位置的引用设为 null,然后在数组中找到下一个可用的位置 h,直到遇到 null。将节点 e 放置在新的位置 h 处。

最终,返回最后一个处理的位置 i

总之,expungeStaleEntry(int staleSlot) 方法用于清除指定位置的过期节点,并重新哈希其他节点,以保持哈希表的连续性。它通过将过期节点的值设为 null,将引用设为 null,并重新哈希其他节点来完成清理操作。


2. replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) :该方法用于替换指定位置 staleSlot 处的过期节点,并维护哈希表的顺序。

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))
        if (e.get() == null)
            slotToExpunge = i;

    for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;

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

            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

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

    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

解释:该方法首先获取 table 数组的引用,并获取数组的长度 len

接下来,通过循环从指定位置 staleSlot 的前一个位置开始向前遍历数组,查找当前运行中是否存在先前的过期节点。在每次迭代中,获取当前节点 e 关联的 ThreadLocal 对象,并检查是否为 null。如果是 null,则更新 slotToExpunge 为当前位置 i

然后,通过循环从指定位置 staleSlot 的下一个位置开始向后遍历数组,查找要么是键(key),要么是运行中的尾部空槽的位置,以先出现的为准。在每次迭代中,获取当前节点 e 关联的 ThreadLocal 对象 k

如果找到了键 key,说明需要将其与过期节点进行交换,以保持哈希表的顺序。将当前位置 i 处的引用设为过期节点的引用,将过期节点的引用设为当前位置 staleSlot 处的引用。如果存在先前的过期节点,将 slotToExpunge 更新为当前位置 i,然后调用 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len) 方法来清除或重新哈希运行中的其他节点,并返回。

如果在向后遍历时没有找到过期节点,则将 slotToExpunge 更新为当前位置 i。这表示在向前遍历时找到的第一个过期节点仍然存在于运行中。

如果未找到键 key,则将指定位置 staleSlot 处的过期节点的值设为 null,并用新的 Entry 对象替换该位置。

最后,如果存在运行中的其他过期节点,则调用 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len) 方法来清除或重新哈希这些节点。

总之,replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot) 方法用于替换指定位置的过期节点,并维护哈希表的顺序。它通过交换过期节点和键 key 的位置来维护哈希表的顺序,并根据情况清除或重新哈希运行中的其他节点。


3. 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 e = tab[i];
        if (e != null && e.get() == null) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ((n >>>= 1) != 0);
    return removed;
}

解释:该方法首先初始化一个布尔变量 removed,表示是否有节点被清除。

接下来,获取 table 数组的引用,并获取数组的长度 len

然后,使用一个循环来迭代清除一些槽位的过期节点。在每次迭代中,通过 nextIndex(i, len) 方法获取下一个槽位的索引 i,并获取该位置处的节点 e

如果节点 e 不为 null 且关联的 ThreadLocal 对象为 null,表示该节点已过期,需要清除。将 len 赋值给变量 n,表示需要重新计算哈希表的长度。将 removed 设置为 true,表示有节点被清除。然后调用 expungeStaleEntry(i) 方法来清除过期节点,并将返回的索引值赋给 i

循环继续执行,直到 n 右移一位后变为 0。每次循环迭代都会处理下一个槽位,直到达到哈希表的末尾。

最后,返回 removed,表示是否有节点被清除。

总之,cleanSomeSlots(int i, int n) 方法用于清除哈希表中一些槽位的过期节点,并返回一个布尔值表示是否有节点被清除。它通过循环迭代遍历哈希表的槽位,检查并清除过期节点。


今夜有点儿凉
40 声望3 粉丝

今夜有点儿凉,乌云遮住了月亮。