并发编程(三)-ThreadLocal源码分析(二)

一、ThreadLocalMap


1.1 ThreadlocalMap 是什么

ThreadLocalMap是一个自定义的哈希表,它的实现方式与HashMap类似。ThreadLocalMap中的每个元素都是一个Entry对象,每个Entry对象包含一个ThreadLocal对象和对应的值。它的键是ThreadLocal对象,值是ThreadLocal在当前线程中存储的值。当多个线程共享一个ThreadLocal对象时,ThreadLocalMap能够为每个线程提供独立的变量副本,确保变量在每个线程中都能够独立地改变而互不影响。ThreadLocalMap的作用是为每个线程提供独立的存储空间,以避免线程之间的干扰和竞争问题。

在Java中,ThreadLocalMap被广泛应用于需要在多个线程中共享一些数据的场景,但是又需要保证线程安全和数据独立性的情况。例如,在Web应用程序中,可以使用ThreadLocalMap来存储用户会话信息,每个线程都有自己的会话数据,互不干扰,从而保证了线程安全和数据独立性。

1.2 ThreadLocalMap 作用

它是ThreadLocal类的内部类,用于存储ThreadLocal在当前线程中存储的值。ThreadLocal是一种可以为多线程应用程序中的每个线程提供独立变量的机制,ThreadLocalMap的作用是为每个线程提供独立的存储空间,以避免线程之间的干扰和竞争问题。ThreadLocalMap是ThreadLocal的重要实现方式之一。具体来说,ThreadLocalMap有以下几个作用:

  1. 提供线程内部的独立存储空间。在多线程环境下,每个线程都有自己的ThreadLocalMap对象,不同的线程之间互不干扰。通过ThreadLocal对象在ThreadLocalMap中存储对应的值,就可以在每个线程中独立存储数据,避免了线程之间的干扰和竞争问题。

  1. 避免线程安全问题。在多线程环境下,如果多个线程共享同一个变量,就会存在线程安全问题,可能会导致数据不一致或者产生竞争问题。通过ThreadLocalMap,每个线程都有自己的存储空间,可以避免线程之间的干扰和竞争问题,从而保证线程安全。

  1. 支持线程内部的数据传递。在多线程环境下,有时需要在线程之间传递数据,而通过ThreadLocalMap可以实现在线程内部传递数据。可以在一个线程中将数据存储到ThreadLocal对象中,然后在另一个线程中通过ThreadLocal对象获取对应的值,这样就可以实现线程内部的数据传递。

总之,ThreadLocalMap的作用是为每个线程提供独立的存储空间,以避免线程之间的干扰和竞争问题,从而保证线程安全,并且支持线程内部的数据传递。

1.3 源码分析

1.3.1 ThreadLocalMap 的 Diagrams
1.3.2 构造函数
1.3.2.1 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)构造函数

构造函数会初始化一个Entry数组,并将ThreadLocal对象及其对应的值存储到数组中的指定位置,用于在当前线程中存储数据。

  1. 初始化一个Entry数组,该数组的长度为INITIAL_CAPACITY。

  1. 通过位运算,计算出ThreadLocal对象在数组中的索引位置,即i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)。threadLocalHashCode是ThreadLocal对象的哈希码,用于计算ThreadLocal对象在数组中的索引位置。由于数组的长度是2的幂次方,因此可以通过位运算来计算ThreadLocal对象在数组中的索引位置,这样可以提高计算效率。

  1. 将ThreadLocal对象及其对应的值存储到Entry数组中,即table[i] = new Entry(firstKey, firstValue)。该操作会创建一个新的Entry对象,并将其存储到Entry数组中的索引位置i处。

  1. size设置为1,表示ThreadLocalMap中当前存储的元素个数为1。

  1. 调用setThreshold(INITIAL_CAPACITY)方法,设置扩容阈值为INITIAL_CAPACITY = 16,用于在元素个数超过阈值时扩容Entry数组。

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 初始化一个Entry数组
    table = new Entry[INITIAL_CAPACITY];
    // 通过位运算,计算出ThreadLocal对象在数组中的索引位置
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 将ThreadLocal对象及其对应的值存储到Entry数组中
    table[i] = new Entry(firstKey, firstValue);
    // 将size设置为1,表示ThreadLocalMap中当前存储的元素个数为1
    size = 1;
    // 设置扩容阈值为 16 * 2/3 
    setThreshold(INITIAL_CAPACITY);
}
1.3.2.2 ThreadLocalMap(ThreadLocalMap parentMap)构造函数

私有构造函数用于创建一个新的ThreadLocalMap对象,并将其初始化为父ThreadLocalMap对象的副本。该操作可以用于在当前线程中创建一个新的ThreadLocalMap对象,并将父线程中的数据复制到新的对象中,以便新线程可以访问父线程中存储的数据。

  1. 获取父ThreadLocalMap对象的Entry数组,即Entry[] parentTable = parentMap.table;

  1. 获取父ThreadLocalMap对象的Entry数组长度;

  1. 设置当前ThreadLoaclMap对象的扩容阈值;

  1. 创建一个新的Entry数组,其长度与父ThreadLocalMap对象的Entry数组长度相同,即table = new Entry[len];

  1. 遍历父ThreadLocalMap对象的Entry数组,对于每个非空的Entry对象,获取其对应的ThreadLocal对象和值;

  1. 通过ThreadLocal对象的childValue方法计算出新的值;

  1. 创建一个新的Entry对象,并将ThreadLocal对象和新的值存储到该对象中,即Entry c = new Entry(key, value);

  1. 计算ThreadLocal对象在新的Entry数组中的索引位置,即int h = key.threadLocalHashCode & (len - 1);

  1. 如果该位置已经存在Entry对象,则通过线性探测法找到下一个空闲位置,即h = nextIndex(h, len);

  1. 将新的Entry对象存储到数组中的空闲位置中,即table[h] = c;

  1. 将当前ThreadLocalMap对象中存储的Entry对象个数加1,即size++

private ThreadLocalMap(ThreadLocalMap parentMap) {
    // 获取父ThreadLocalMap对象的Entry数组
    Entry[] parentTable = parentMap.table;
    // 获取父ThreadLocalMap对象的Entry数组长度
    int len = parentTable.length;
    // 设置当前ThreadLoaclMap对象的扩容阈值
    setThreshold(len);
    // 设置与父ThreadLocalMap 对象的Entry数组长度相同的 新Entry数组
    table = new Entry[len];
  
    // 遍历父ThreadLocalMap对象的Entry数组
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        // Entry数组非空
        if (e != null) {
            @SuppressWarnings("unchecked")
            // 获取其对应的ThreadLocal对象
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                // 通过ThreadLocal对象的childValue方法计算出新的值
                Object value = key.childValue(e.value);
                // 创建一个新的Entry对象,并将ThreadLocal对象和新的值存储到该对象中
                Entry c = new Entry(key, value);
                // 计算ThreadLocal对象在新的Entry数组中的索引位置
                int h = key.threadLocalHashCode & (len - 1);
                // 如果该位置已经存在Entry对象,
                while (table[h] != null)
                    // 通过线性探测法找到下一个空闲位置
                    h = nextIndex(h, len);
                // 将新的Entry对象存储到数组中的空闲位置中
                table[h] = c;
                // 将当前ThreadLocalMap对象中存储的Entry对象个数加1
                size++;
            }
        }
    }
}
1.3.3 属性
// INITIAL_CAPACITY 是 ThreadLocalMap 中用于初始化哈希表容量的静态常量,它影响着哈希表的性能和效率。
private static final int INITIAL_CAPACITY = 16;

// table 数组是 ThreadLocalMap 用于存储每个线程对应的变量值的哈希表,其中的每个元素都是一个键值对(Entry 对象)。
private Entry[] table;

// size 属性是 ThreadLocalMap 中用于记录当前哈希表中键值对数量的属性,用于哈希表的扩容和性能优化。
private int size = 0;

// threshold 属性是 ThreadLocalMap 中用于记录哈希表容量阈值的属性,用于哈希表的扩容和性能优化
private int threshold;
  • table:在 ThreadLocalMap 中,通过哈希表实现键值对的查找和添加。具体来说,每个 ThreadLocal 对象都有一个唯一的 hashcode 值,ThreadLocalMap 使用这个值作为键在哈希表中查找对应的 Entry 对象。如果找到了对应的 Entry 对象,则更新其值;否则,创建一个新的 Entry 对象,并将其插入到哈希表中。

  • size:属性的主要作用是用于哈希表的扩容。当哈希表中的键值对数量超过了负载因子与容量的乘积时,哈希表会自动扩容。在扩容时,哈希表会重新计算哈希码并重新分配每个键值对的位置,这个过程需要耗费一定的时间和空间。为了避免频繁的扩容操作,哈希表的容量要足够大,而 size 属性的值则可以用来判断当前哈希表的实际负载因子,从而决定是否需要进行扩容操作。

  • threshold :属性的主要作用是用于哈希表的扩容。当哈希表中的键值对数量超过了 threshold 时,哈希表会自动扩容。在扩容时,哈希表会重新计算哈希码并重新分配每个键值对的位置,这个过程需要耗费一定的时间和空间。为了避免频繁的扩容操作,哈希表的容量要足够大,而 threshold 属性的值则可以用来判断当前哈希表的实际负载因子,从而决定是否需要进行扩容操作。

1.3.4 entry对象

静态内部类Entry继承自WeakReference<ThreadLocal<?>>,表示对ThreadLocal对象的弱引用,并包含了一个与该ThreadLocal对象相关联的值value,用于存储线程私有变量的值。

在构造方法 Entry(ThreadLocal<?> k, Object v) 中,它首先调用父类构造方法 super(k) 来创建一个 ThreadLocal 对象的弱引用,并将其保存在 Entry 对象中。然后,它将与该 ThreadLocal 对象关联的值 v 保存在 value 属性中。

WeakReference是Java提供的一种弱引用类型,当一个对象只被弱引用指向时,如果内存空间不足,系统会自动回收该对象。因此,当一个ThreadLocal对象在所有线程中都没有被引用时,它会被自动回收,进而导致ThreadLocalMap中相关的Entry对象也被回收。这是ThreadLocal实现线程私有变量的关键,因为它能够避免出现内存泄漏的情况。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
1.3.5 方法
1.3.5.1 rehash()方法

rehash() 方法是 ThreadLocalMap 中用于扩容哈希表的方法,它会先删除过期的键值对,然后根据当前哈希表的负载因子判断是否需要进行扩容操作。

// 哈希表的扩容操作,用于增加哈希表的容量。
private void rehash() {
    // 用于删除过期的键值对
    expungeStaleEntries();

  // 判断当前哈希表的负载因子是否超过了阈值的 0.75 倍
    if (size >= threshold - threshold / 4)
       // 调用 resize() 方法进行哈希表的扩容
        resize();
}
1.3.5.2 resize()方法、
resize() 方法是 ThreadLocalMap 中用于扩容哈希表的方法,它会将原哈希表中的键值对重新分配到新哈希表中,并更新哈希表的阈值、键值对数量和数组。
  1. 获取原哈希表的数组 oldTab,并获取其长度 oldLen

  1. 计算出新的数组长度 newLen,即原长度的两倍;

  1. 创建一个新的数组 newTab,其长度为 newLen;

  1. 定义一个计数器 count,用于统计哈希表中的键值对数量;

  1. 循环遍历,将原哈希表中的键值对重新分配到新哈希表中;

  1. 对于原哈希表中的每个元素 e,如果 e 不为 null,则获取其对应的 ThreadLocal 对象 k。

  1. 如果 k 为 null,则说明 e 对应的 ThreadLocal 对象已经被回收,因此将 e.value 设为 null,以便帮助垃圾回收机制回收这个对象。

  1. 重新计算 k 的哈希码 h,并找到新哈希表中对应的位置。

  1. 如果该位置已经被占用,则通过 nextIndex() 方法找到下一个空闲位置。

  1. 将 e 放入新哈希表的对应位置,并将计数器 count 加一。

  1. 在循环结束后,更新哈希表的阈值 threshold、键值对数量 size 和数组 table。具体来说,将阈值设为新数组的长度,键值对数量设为计数器 count,数组设为新数组 newTab。

// 哈希表的扩容操作,用于增加哈希表的容量。
private void resize() {
    // 获取原哈希表的数组 oldTab
    Entry[] oldTab = table;
    // 并获取其长度 oldLen
    int oldLen = oldTab.length;
    // 原长度的两倍
    int newLen = oldLen * 2;
    // 创建一个新的数组 newTab
    Entry[] newTab = new Entry[newLen];
    // 定义一个计数器 `count`,用于统计哈希表中的键值对数量
    int count = 0;
  
    // 循环遍历
    for (int j = 0; j < oldLen; ++j) {
        // 将原哈希表中的键值对重新分配到新哈希表中
        Entry e = oldTab[j];
        if (e != null) {
            // 获取其对应的 ThreadLocal 对象 k
            ThreadLocal<?> k = e.get();
            // 如果 k 为 null
            if (k == null) {
                // 则说明 e 对应的 ThreadLocal 对象已经被回收,将e.value设为 null,以便帮助垃圾回收机制回收这个对象。
                e.value = null; 
            } else {
                // 重新计算 `k` 的哈希码 `h`,并找到新哈希表中对应的位置
                int h = k.threadLocalHashCode & (newLen - 1);
                // 如果该位置已经被占用
                while (newTab[h] != null)
                    // 通过 `nextIndex()` 方法找到下一个空闲位置
                    h = nextIndex(h, newLen);
                // 将 e 放入新哈希表的对应位置
                newTab[h] = e;
               // 计数器 count加一。
                count++;
            }
        }
    }
  // 更新哈希表的阈值
    setThreshold(newLen);
    // 键值对数量 
    size = count;
    // 数组
    table = newTab;
}
1.3.5.3 expungeStaleEntries()方法
expungeStaleEntry()方法是 ThreadLocalMap 中用于清除过期键值对的方法,它会遍历哈希表中的所有元素,对于已经被回收的 ThreadLocal 对象对应的键值对,调用 expungeStaleEntry(j) 方法进行删除。
  1. 获取哈希表的数组 tab,以及数组的长度 len;

  1. 对于数组中的每个元素 e,如果 e 不为 null 且它对应的 ThreadLocal 对象已经被回收(即 e.get() == null),则调用 expungeStaleEntry(j) 方法删除这个键值对;

  1. expungeStaleEntry(j) 方法的作用是删除索引为 j 的键值对,并且通过将索引为 j 的元素设为 null,帮助垃圾回收机制回收这个对象。具体来说,该方法会将索引为 j 的元素设为 null,并通过循环,将后面的元素向前移动,以填补这个空缺位置。最后,如果数组的最后一个元素为 null,则将其设为 nullEntry,以便下次插入元素时可以快速定位到空闲位置。

private void expungeStaleEntries() {
    // 获取哈希表的数组 `tab`
    Entry[] tab = table;
    // 获取数组的长度 `len`
    int len = tab.length;
    // 遍历数组
    for (int j = 0; j < len; j++) {
        // 获取数组中每个元素e
        Entry e = tab[j];
        // 如果 e 不为 null 且它对应的 ThreadLocal 对象已经被回收
        if (e != null && e.get() == null)
            // 调用expungeStaleEntry(j) 方法删除这个键值对;
            expungeStaleEntry(j);
    }
}
1.3.5.4 expungeStaleEntry(int staleSlot) 方法

expungeStaleEntry(int staleSlot) 方法是 ThreadLocalMap 中用于清除过期键值对的方法,它会删除索引为 staleSlot 的键值对,并重新哈希其他元素,直到遇到 null 为止。

该方法会执行以下几个步骤:

  1. 获取哈希表的数组 tab,以及数组的长度 len。

  1. 将索引为 staleSlot 的元素设为 null,并将 size 减 1。

  1. 从 staleSlot 的下一个位置开始遍历数组,直到遇到 null 为止;

  1. 获取e对应的 ThreadLocal 对象,如果已经被回收,则将其值设为 null,将其在数组中的位置设为 null,并将 size 减 1。

  1. 否则,计算 e 所对应的 ThreadLocal 对象在数组中的哈希值 h,如果 h 不等于 i,则说明 e 在数组中的位置不正确,需要重新哈希。具体来说,我们需要在数组中从 h 开始往后查找,直到找到第一个 null 为止,然后将 e 放到该位置上。

  1. 返回下一个需要遍历的位置 i。

// ThreadLocalMap中用于清除过期键值对的方法,它会删除索引为staleSlot的键值对,并重新哈希其他元素,直到遇到null为止。
private int expungeStaleEntry(int staleSlot) {
    // 获取哈希表的数组
    Entry[] tab = table;
    // 数组的长度
    int len = tab.length;

    // 将索引为 staleSlot 的元素设为 null
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    //  `size` 减 1
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    // 从staleSlot 的下一个位置开始遍历数组,直到遇到 null为止
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        // 获取e对应的 ThreadLocal 对象,如果已经被回收,
        ThreadLocal<?> k = e.get();
        if (k == null) {
            // 则将其值设为 null
            e.value = null;
            // 将其在数组中的位置设为 null
            tab[i] = null;
            // 并将 size 减 1
            size--;
        } else {
            // 计算 e所对应的 ThreadLocal 对象在数组中的哈希值 h
            int h = k.threadLocalHashCode & (len - 1);
            // 如果 h 不等于 i,则说明 e 在数组中的位置不正确,需要重新哈希。
            if (h != i) {
                tab[i] = null;

                // 在数组中从h 开始往后查找,直到找到第一个 null 为止
                while (tab[h] != null)
                    h = nextIndex(h, len);
                // 将 `e` 放到该位置
                tab[h] = e;
            }
        }
    }
    // 返回下一个需要遍历的位置 i
    return i;
}
1.3.5.5 getEntry(ThreadLocal<?> key) 方法

getEntry(ThreadLocal<?> key) 方法是 ThreadLocalMap 中获取指定 ThreadLocal 对象对应的键值对的方法,它会先计算出 key 在数组中的索引 i,然后查找索引为 i 的元素,如果找到则直接返回,否则调用 getEntryAfterMiss(key, i, e) 方法继续查找。

  1. 计算 key 在数组中的索引 i,具体计算方法是将 key 的 threadLocalHashCode 与 (table.length - 1) 进行按位与操作。

  1. 获取数组中索引为 i 的元素 e,

  1. 如果 e 不为 null 且它所对应的 ThreadLocal 对象就是 key,则直接返回 e。

  1. 如果 e 为 null 或者它所对应的 ThreadLocal 对象不是 key,则调用 getEntryAfterMiss(key, i, e) 方法继续查找。具体来说,该方法会从索引 i 的下一个位置开始遍历数组,直到找到第一个值为 null 或者它所对应的 ThreadLocal 对象就是 key 的元素为止。如果找到了对应的元素,则返回该元素;否则,返回 null。

private Entry getEntry(ThreadLocal<?> key) {
  // 计算 `key` 在数组中的索引 `i`
    int i = key.threadLocalHashCode & (table.length - 1);
    // 获取数组中索引为 `i` 的元素 `e`
    Entry e = table[i];
  // 如果 `e` 不为 null 且它所对应的 `ThreadLocal` 对象就是 `key`
    if (e != null && e.get() == key)
      // 则直接返回 `e`
        return e;
    else
        // 如果 `e` 为 null 或者它所对应的 `ThreadLocal` 对象不是 `key`,则调用 `getEntryAfterMiss(key, i, e)` 方法继续查找
        return getEntryAfterMiss(key, i, e);
}
1.3.5.6 getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) 方法

在查找ThreadLocal对应的Entry时,如果第一次查找未命中,则在表中的下一个位置继续查找,直到找到与指定ThreadLocal对应的Entry或者表中不存在对应的Entry为止。

  1. 获取ThreadLocalMap中的table和table的长度len。

  1. 循环遍历Entry对象;

  1. 如果Entry对象的ThreadLocal对象与要查找的key相等,则返回此Entry对象;

  1. 如果当前Entry对象的ThreadLocal对象为null,说明此Entry对象已经过期,需要将其删除;

  1. 如果当前Entry对象的ThreadLocal对象不是要查找的key,则通过nextIndex方法获取下一个索引位置;

  1. 继续查找下一个Entry对象,直到遍历完整个table数组;

  1. 如果没有找到对应的Entry对象,则返回null。

// 循环遍历Entry对象
/**
*   key:要查找的ThreadLocal对象。
*   i:从指定索引位置开始查找。
*    e:从这个Entry对象开始查找。
**/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    // 获取ThreadLocalMap中的table
    Entry[] tab = table;
    // table的长度len。
    int len = tab.length;
  // 循环遍历Entry对象
    while (e != null) {
        ThreadLocal<?> k = e.get();
        // 如果Entry对象的ThreadLocal对象与要查找的key相等,则返回此Entry对象。
        if (k == key)
            return e;
        // 如果当前Entry对象的ThreadLocal对象为null,说明此Entry对象已经过期,需要将其删除。
        if (k == null)
            expungeStaleEntry(i);
        else
            // 如果当前Entry对象的ThreadLocal对象不是要查找的key,则通过nextIndex方法获取下一个索引位置。
            i = nextIndex(i, len);
        // 继续查找下一个Entry对象,直到遍历完整个table数组
        e = tab[i];
    }
    // 如果没有找到对应的Entry对象,则返回null。
    return null;
}

1.3.5.7 remove(ThreadLocal<?> key) 方法

用于从ThreadLocalMap中删除指定的ThreadLocal对象对应的Entry对象。

  1. 获取 ThreadLocalMap 中的 table 数组;

  1. 计算指定 ThreadLocal 对象的哈希码,并使用该哈希码与数组长度进行按位与运算来获取该对象在数组中的索引位置;

  1. 遍历该索引位置上的Entry对象,如果找到与要删除的ThreadLocal对象相等的Entry对象,则调用clear方法将该Entry对象的值清空,并调用expungeStaleEntry方法删除该Entry对象;

  1. 最后,该方法结束执行。

// 从ThreadLocalMap中删除指定的ThreadLocal对象对应的Entry对象,如果找到则清空该Entry对象的值并删除该Entry对象,否则不进行任何操作。
private void remove(ThreadLocal<?> key) {
  // table 数组
    Entry[] tab = table;
    // table数组的长度
    int len = tab.length;
    // 根据threadLocalHashCode计算在table数组中的索引位置。
    int i = key.threadLocalHashCode & (len-1);
    // 遍历索引位置上的entry对象,
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        // 找到与要删除的`ThreadLocal`对象相等的`Entry`对象
        if (e.get() == key) {
            // 调用`clear`方法将该`Entry`对象的值清空
            e.clear();
            // 调用`expungeStaleEntry`方法删除该`Entry`对象。
            expungeStaleEntry(i);
            return;
        }
    }
}
1.3.5.8 set(ThreadLocal<?> key, Object value)方法

将指定的ThreadLocal对象与对应的值存储到当前线程的ThreadLocalMap中。

  1. 它首先获取 ThreadLocalMap 中的 table 数组及长度,

  1. 根据threadLocalHashCode计算在table数组中的索引位置。

  1. 遍历该索引位置上的Entry对象,如果找到与要存储的ThreadLocal对象相等的Entry对象,则将该Entry对象的值替换为要存储的值,并返回。

如果找到的 Entry 对象的 key 为 null,说明该 Entry 对象已经被回收了,则调用replaceStaleEntry方法替换该Entry对象。


  1. 如果遍历整个table数组,都没有找到与要存储的ThreadLocal对象相等的Entry对象,则创建一个新的Entry对象,并存储到该索引位置上。

  1. 检查是否需要清理一些过期的 Entry 对象,如果需要,则调用 cleanSomeSlots 方法来清理一些过期的 Entry 对象;

  1. 如果 size 大于等于 threshold,说明 table 数组的负载因子已经达到了上限,那么就调用 rehash 方法来重新调整 table 数组的大小。

// key:要存储的ThreadLocal对象。value:与key对应的值
private void set(ThreadLocal<?> key, Object value) {
  // table 数组
    Entry[] tab = table;
    // table 数组的长度
    int len = tab.length;
    // 根据threadLocalHashCode计算在table数组中的索引位置
    int i = key.threadLocalHashCode & (len-1);
  
    // 遍历该索引位置上的`Entry`对象
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
    // 如果找到与要存储的ThreadLocal对象相等的Entry对象
        if (k == key) {
            // 将该Entry对象的值替换为要存储的值,并返回。
            e.value = value;
            return;
        }
    // 如果找到的 Entry 对象的 key 为 null,说明该 Entry 对象已经被回收了
        if (k == null) {
            // 调用replaceStaleEntry方法替换该Entry对象
            replaceStaleEntry(key, value, i);
            return;
        }
    }
  // 如果遍历整个table数组,都没有找到与要存储的ThreadLocal对象相等的Entry对象,则创建一个新的Entry对象,并存储到该索引位置上。
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 检查是否需要清理一些过期的 Entry 对象,如果需要,则调用 cleanSomeSlots 方法来清理一些过期的 Entry 对象
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        // 如果 size 大于等于 threshold,说明 table 数组的负载因子已经达到了上限,那么就调用 rehash 方法来重新调整 table 数组的大小。
        rehash();
}
1.3.5.9 cleanSomeSlots(int i, int n) 方法

用于清理 ThreadLocalMap 中一些过期的 Entry 对象。

  1. 获取 ThreadLocalMap 中的 table 数组,

  1. 循环遍历table数组,从i开始,每次取下一个索引位置上的Entry对象;

  1. 如果该Entry对象不为null,并且其对应的ThreadLocal对象为null,则说明该Entry对象已经过期,需要被删除。调用expungeStaleEntry方法删除该Entry对象,并将removed标记设置为true。

  1. 环会一直执行,直到 n 变为 0 为止。

  1. 返回是否成功删除过期的Entry对象。

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    // 获取table数组
    Entry[] tab = table;
    // table的长度
    int len = tab.length;
    do {
        // 每次取下一个索引位置上的`Entry`对象
        i = nextIndex(i, len);
        Entry e = tab[i];
        // 如果该Entry对象不为null,并且其对应的ThreadLocal对象为null,则说明该`Entry`对象已经过期,需要被删除
        if (e != null && e.get() == null) {
            n = len;
            // 将removed标记设置为true
            removed = true;
            // 调用expungeStaleEntry方法删除该Entry对象
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}
1.3.5.10 replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot)方法

用于替换过期的Entry对象。

  1. 获取table数组和table的长度;

  1. 在staleSlot之前的位置上查找最近的一个已经过期的Entry对象的索引位置,将其赋值给slotToExpunge;

  1. 在staleSlot之后的位置上循环查找,每次取下一个索引位置上的Entry对象;

  1. 如果找到了一个Entry对象,且该对象对应的ThreadLocal对象与key相等,则说明找到了要替换的Entry对象,将其值赋值为value,并将其和staleSlot位置上的Entry对象交换位置,然后调用expungeStaleEntry方法删除slotToExpunge和之后的过期Entry对象。

  1. 如果找到了一个Entry对象,且该对象对应的ThreadLocal对象为null,则将其索引位置赋值给slotToExpunge。

  1. 如果没有找到要替换的Entry对象,则在staleSlot位置上创建一个新的Entry对象,并将其值设置为value。

  1. 如果slotToExpunge不等于staleSlot,则调用expungeStaleEntry方法删除slotToExpunge和之后的过期Entry对象。

// key:要替换的Entry对应的ThreadLocal对象;
// value:要替换的值。
// staleSlot:要替换的Entry对象在table数组中的索引位置。方法内部的实现逻辑如下:
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    // 获取table数组
    Entry[] tab = table;
    // table的长度
    int len = tab.length;
    Entry e;

    int slotToExpunge = staleSlot;
    // 在staleSlot之前的位置上查找最近的一个已经过期的Entry对象的索引位置,将其赋值给slotToExpunge。
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        // 已经过期的Entry对象
        if (e.get() == null)
            slotToExpunge = i;

  // 在staleSlot之后的位置上循环查找,
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        // 每次取下一个索引位置上的Entry对象。
        ThreadLocal<?> k = e.get();
    // 该对象对应的ThreadLocal对象与key相等,则说明找到了要替换的Entry对象,
        if (k == key) {
            // 其值赋值为value,
            e.value = value;
      // 并将其和staleSlot位置上的Entry对象交换位置
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;
      // 调用expungeStaleEntry方法删除slotToExpunge和之后的过期Entry对象。
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }
    // 如果找到了一个Entry对象,且该对象对应的ThreadLocal对象为null,则将其索引位置赋值给slotToExpunge。
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }
  // 如果没有找到要替换的Entry对象,则在staleSlot位置上创建一个新的Entry对象,并将其值设置为value。
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 如果slotToExpunge不等于staleSlot,则调用expungeStaleEntry方法删除slotToExpunge和之后的过期Entry对象。
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值