【Java原理系列】 Java ThreadLocal 原理源码详解

Java ThreadLocal 原理源码详解

原理

ThreadLocal 是 Java 中的一个线程局部变量类,它提供了一种将数据与线程关联起来的机制。每个线程都可以独立地访问自己的线程局部变量,且线程之间互不干扰。

ThreadLocal 的原理是基于线程内部的 ThreadLocalMap 实现的。在 Thread 类中有一个名为 threadLocals 的成员变量,它是一个 ThreadLocalMap 对象,用于存储每个线程的线程局部变量。ThreadLocalMap 是一个自定义的哈希表,其键为 ThreadLocal 对象,值为对应线程的变量副本。

当使用 ThreadLocalget() 方法获取线程局部变量时,实际上是通过当前线程的 threadLocals 成员变量找到对应的 ThreadLocalMap,再根据 ThreadLocal 对象作为键来获取对应的值。

当使用 ThreadLocalset() 方法设置线程局部变量时,也是通过当前线程的 threadLocals 成员变量找到对应的 ThreadLocalMap,再将 ThreadLocal 对象和要设置的值存入其中。

总结起来,ThreadLocal 通过维护每个线程的 ThreadLocalMap 来实现线程局部变量的存取,并确保每个线程只能访问自己的变量副本。

示例

下面是一个使用 ThreadLocal 的示例:

public class ThreadLocalExample {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set("Hello, ThreadLocal!");

        Thread thread1 = new Thread(() -> {
            String value = threadLocal.get();
            System.out.println(value); // 输出:Hello, ThreadLocal!
        });

        Thread thread2 = new Thread(() -> {
            String value = threadLocal.get();
            System.out.println(value); // 输出:null
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

在上述示例中,我们创建了一个 ThreadLocal 对象,并通过 set() 方法设置了线程局部变量的值。然后创建了两个线程,分别在不同的线程中通过 get() 方法获取线程局部变量的值。可以看到,在每个线程中,通过 get() 方法获取的线程局部变量的值是独立的,互不干扰。

需要注意的是,ThreadLocal 应该在每个线程使用结束后进行清理,以避免内存泄漏问题。可以通过调用 remove() 方法来清理当前线程的线程局部变量,或者使用 ThreadLocalinitialValue() 方法来指定默认值,使得线程使用完毕后自动回收。

方法总结

ThreadLocal类

ThreadLocal类提供了线程本地变量的功能。这些变量与普通变量的区别在于,每个访问它们(通过其getset方法)的线程都有自己独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程关联起来的类中的私有静态字段(例如,用户ID或事务ID)。

以下是ThreadLocal类的一些重要方法和概念:

  • get(): 获取当前线程的线程本地变量副本的值。如果当前线程没有该变量的值,则首次访问时会调用initialValue方法进行初始化。
  • set(T value): 设置当前线程的线程本地变量副本的值。
  • remove(): 删除当前线程的线程本地变量副本的值。如果之后再次通过get方法读取该变量,将会重新调用initialValue方法进行初始化。
  • initialValue(): 返回线程本地变量的“初始值”。第一次使用get方法访问变量时将调用此方法,除非线程之前已经调用过set方法,否则将不会为该线程调用initialValue方法。默认情况下,该方法返回null,可以通过子类化ThreadLocal类并重写该方法来指定线程本地变量的初始值。

ThreadLocal类内部使用了ThreadLocalMap来管理线程本地变量的映射关系。ThreadLocalMap是一个自定义的哈希表,用于存储ThreadLocal对象和与其相关联的值。它使用线性探测法解决哈希冲突,并且在需要时调整大小和重新散列。每个线程都有自己的ThreadLocalMap实例,可以通过Thread.currentThread().threadLocals来访问。

需要注意的是,ThreadLocal类中的线程本地变量副本只在当前线程活动并且ThreadLocal实例可访问时保持隐式引用。一旦线程终止,所有线程本地变量的副本都将被垃圾回收(除非存在对这些副本的其他引用)。

ThreadLocal类还提供了一个静态工厂方法withInitial(Supplier<? extends S> supplier),用于创建具有初始值的线程本地变量。初始值由传入的Supplier函数提供。

上述代码示例展示了如何使用ThreadLocal类生成每个线程独立的唯一标识符。每个线程的id是在第一次调用ThreadId.get()方法时分配的,并且在后续调用中保持不变。

ThreadLocalMap类

ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。没有将任何操作导出到ThreadLocal类之外。该类的访问修饰符为包私有,以允许在Thread类中声明字段。为了处理非常大且长时间存在的使用情况,哈希表条目使用WeakReferences作为键。但由于没有使用引用队列,只有当表开始耗尽空间时才保证删除过期条目。

以下是ThreadLocalMap类的一些重要方法和概念:

  • Entry: ThreadLocalMap中的条目(Entry)扩展了WeakReference,使用其主要引用字段作为键(始终是ThreadLocal对象)。注意,空键(即entry.get() == null)意味着该键不再被引用,因此可以从表中删除该条目。在代码中,这些条目被称为“过期条目”。

  • table: 表示哈希映射的数组,根据需要调整大小。table.length必须始终是2的幂。

  • size: 表中条目的数量。

  • threshold: 下一次需要调整大小的阈值。

  • setThreshold(int len): 设置调整大小的阈值,以保持最坏情况下的2/3负载因子。

  • nextIndex(int i, int len): 对i进行递增操作,并对len取模,得到下一个索引。

  • prevIndex(int i, int len): 对i进行递减操作,并对len取模,得到上一个索引。

  • ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue): 构造函数,用于初始化包含(firstKey, firstValue)的新映射。ThreadLocalMaps是延迟构建的,所以只有在有至少一个条目需要放入时才会创建一个。

  • ThreadLocalMap(ThreadLocalMap parentMap): 构造函数,用于包括给定父映射中的所有Inheritable ThreadLocals。仅被createInheritedMap方法调用。

  • getEntry(ThreadLocal<?> key): 获取与指定键关联的条目。此方法处理快速路径:直接命中现有键的情况。否则,将传递给getEntryAfterMiss方法。这样设计是为了最大限度地提高直接命中的性能。

  • getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e): 在key未在其直接哈希槽中找到的情况下使用的getEntry方法的版本。

  • set(ThreadLocal<?> key, Object value): 设置与指定键关联的值。

  • remove(ThreadLocal<?> key): 删除与指定键关联的条目。

  • replaceStaleEntry(ThreadLocal<?> key, Object value, int staleSlot): 用指定键的条目替换遇到的过期条目。无论是否已经存在指定键的条目,都会将value参数存储在条目中。

  • expungeStaleEntry(int staleSlot): 通过重新散列位于staleSlot和下一个空槽之间的可能冲突条目,来删除过期条目。这也会清除遇到的所有其他过期条目。参考Knuth的第6.4节。

  • cleanSomeSlots(int i, int n): 启发式地扫描一些单元格,寻找过期条目。这在添加新元素或已经删除另一个过期条目时被调用。它执行对数数量的扫描,作为不进行扫描(快速但保留垃圾)和与元素数量成比例的扫描次数之间的平衡,后者将导致某些插入需要O(n)时间。

  • rehash(): 重新打包和/或调整表的大小。首先扫描整个表以删除过期条目。如果这不能使表的大小足够缩小,那么将表的大小加倍。

  • resize(): 增加表的容量。

  • expungeStaleEntries(): 清除表中的所有过期条目。

以上是ThreadLocalMap类的主要方法和概念,该类负责管理ThreadLocal对象和与其关联的值的映射关系。

中文源码

/**
 * 此类提供线程本地变量。这些变量与普通变量的区别在于,每个访问它们(通过其{@code get}或{@code set}方法)的线程都有自己独立初始化的变量副本。
 * {@code ThreadLocal}实例通常是希望将状态与线程关联起来的类中的私有静态字段(例如,用户ID或事务ID)。
 *
 * <p>例如,下面的类生成每个线程本地的唯一标识符。
 * 线程的id在首次调用{@code ThreadId.get()}时被分配,并且在后续调用中保持不变。
 * <pre>
 * import java.util.concurrent.atomic.AtomicInteger;
 *
 * public class ThreadId {
 *     // 包含要分配的下一个线程ID的原子整数
 *     private static final AtomicInteger nextId = new AtomicInteger(0);
 *
 *     // 包含每个线程ID的线程本地变量
 *     private static final ThreadLocal<Integer> threadId =
 *         new ThreadLocal<Integer>() {
 *             &#64;Override protected Integer initialValue() {
 *                 return nextId.getAndIncrement();
 *         }
 *     };
 *
 *     // 返回当前线程的唯一ID,如果必要,进行分配
 *     public static int get() {
 *         return threadId.get();
 *     }
 * }
 * </pre>
 * <p>只要线程处于活动状态并且可以访问{@code ThreadLocal}实例,每个线程都持有对其线程本地变量副本的隐式引用;
 * 在线程消失后,线程本地实例的所有副本都将受到垃圾回收的影响(除非存在对这些副本的其他引用)。
 *
 * 作者:Josh Bloch and Doug Lea
 * @since   1.2
 */
public class ThreadLocal<T> {
    /**
     * ThreadLocal依赖于每个线程的线性探测哈希映射(Thread.threadLocals和inheritableThreadLocals)。
     * ThreadLocal对象充当键,通过threadLocalHashCode进行搜索。这是一个自定义的哈希码(仅在ThreadLocalMaps中有用),
     * 它可以在常见情况下消除连续构造的ThreadLocal之间的冲突,因为它们被同一线程使用,同时在不常见的情况下保持良好行为。
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 下一个要分配的哈希码,以原子方式更新。初始值为零。
     */
    private static AtomicInteger nextHashCode =
        new AtomicInteger();

    /**
     * 连续生成的哈希码之间的差异 - 将隐式顺序线程本地ID转换为针对大小为2的幂表的近乎最优的散列值。
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * 返回下一个哈希码。
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

    /**
     * 返回当前线程的线程本地变量的“初始值”。除非线程先前调用了{@link #set}方法,
     * 否则将在首次使用{@link #get}方法访问变量时为线程调用此方法。通常,该方法每个线程最多调用一次,
     * 但在后续调用{@link #remove}后又调用了{@link #get}的情况下可能会再次调用此方法。
     *
     * <p>此实现只是返回{@code null};如果程序员希望线程本地变量具有与{@code null}不同的初始值,
     * 必须对ThreadLocal进行子类化,并覆盖此方法。通常,将使用匿名内部类。
     *
     * @return 线程本地变量的初始值
     */
    protected T initialValue() {
        return null;
    }

    /**
     * 创建一个线程本地变量。变量的初始值通过在{@code Supplier}上调用{@code get}方法确定。
     *
     * @param <S> 线程本地变量的类型
     * @param supplier 用于确定初始值的供应者
     * @return 新的线程本地变量
     * @throws NullPointerException 如果指定的供应商为空
     * @since 1.8
     */
    public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        return new SuppliedThreadLocal<>(supplier);
    }

    /**
     * 创建一个线程本地变量。
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * 返回当前线程的线程本地变量副本的值。如果变量对于当前线程没有值,
     * 则首先将其初始化为通过{@link #initialValue}方法返回的值。
     *
     * @return 当前线程的线程本地变量的值
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    /**
     * 在设置初始值的情况下,与set()方法进行区别。用于替换set()方法,
     * 以防用户覆盖了set()方法。
     *
     * @return 初始值
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    /**
     * 将当前线程的线程本地变量副本设置为指定的值。
     * 大多数子类不需要覆盖此方法,仅依靠{@link #initialValue}方法设置线程本地变量的值。
     *
     * @param value 要存储在当前线程的线程本地副本中的值
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    /**
     * 删除当前线程的线程本地变量的值。如果此后当前线程通过{@link #get}读取该变量,
     * 则将通过调用其{@link #initialValue}方法重新初始化其值,除非当前线程在此期间通过{@link #set}设置了它的值。
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

    /**
     * 获取与ThreadLocal关联的映射。在InheritableThreadLocal中重写。
     *
     * @param  t 当前线程
     * @return 映射
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * 创建与ThreadLocal关联的映射。在InheritableThreadLocal中重写。
     *
     * @param t 当前线程
     * @param firstValue 映射的初始条目的值
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

    /**
     * 为继承的线程本地变量创建映射的工厂方法。
     * 仅设计为从Thread构造函数中调用。
     *
     * @param  parentMap 父线程关联的映射
     * @return 包含父线程可继承绑定的映射
     */
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

    /**
     * childValue方法在子类InheritableThreadLocal中可见定义,
     * 但在这里内部定义,以提供createInheritedMap工厂方法,而无需对InheritableThreadLocal中的映射类进行子类化。
     * 这种技术优于在方法中嵌入instanceof测试。
     */
    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

    /**
     * 一个ThreadLocal的扩展,它从指定的{@code Supplier}获取初始值。
     */
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {

        private final Supplier<? extends T> supplier;

        SuppliedThreadLocal(Supplier<? extends T> supplier) {
            this.supplier = Objects.requireNonNull(supplier);
        }

        @Override
        protected T initialValue() {
            return supplier.get();
        }
    }
}

static class ThreadLocalMap {

    /**
     * ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。
     * 在ThreadLocal类之外不导出任何操作。该类的访问修饰符为包私有,
     * 以允许在Thread类中声明字段。为了处理非常大且长时间存在的使用情况,
     * 哈希表条目使用WeakReferences作为键。但由于没有使用引用队列,
     * 只有当表开始耗尽空间时才保证删除过期条目。
     */
     
    // 条目(Entry)在哈希映射中扩展了WeakReference,
    // 使用主要引用字段作为键(始终是ThreadLocal对象)。
    // 空键(即entry.get() == null)意味着该键不再被引用,
    // 因此可以从表中删除该条目。这些条目被称为“过期条目”。
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** 与此ThreadLocal关联的值 */
        Object value;

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

    /** 初始容量,必须是2的幂次方 */
    private static final int INITIAL_CAPACITY = 16;

    /** 表,根据需要调整大小,table.length必须始终是2的幂 */
    private Entry[] table;

    /** 表中的条目数 */
    private int size = 0;

    /** 下一次调整大小的阈值 */
    private int threshold; // 默认为0

    /**
     * 设置调整大小的阈值,以保持最坏情况下的2/3负载因子。
     */
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    /**
     * 将i增加1并对len取模。
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    /**
     * 将i递减1并对len取模。
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

    /**
     * 构造一个新映射,最初包含(firstKey,firstValue)。
     * ThreadLocalMaps是延迟构建的,所以只有在有至少一个条目需要放入时才会创建一个。
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * 构造一个新映射,包括给定父映射中的所有Inheritable ThreadLocals。
     * 仅被createInheritedMap方法调用。
     *
     * @param 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 e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

    /**
     * 获取与指定键关联的条目。此方法处理快速路径:直接命中现有键。
     * 否则,将传递给getEntryAfterMiss方法。这样设计是为了最大限度地提高直接命中的性能。
     *
     * @param key 线程本地对象
     * @return 与键关联的条目,如果没有则返回null
     */
    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);
    }

    /**
     * 当键在其直接哈希槽中未找到时使用的getEntry方法的版本。
     *
     * @param key 线程本地对象
     * @param i 键的哈希码对应的表索引
     * @param e table[i]处的条目
     * @return 与键关联的条目,如果没有则返回null
     */
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;

        while (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == key)
                return e;
            if (k == null)
                expungeStaleEntry(i);
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

    /**
     * 设置与指定键关联的值。
     *
     * @param key 线程本地对象
     * @param value 要设置的值
     */
    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();
    }

    /**
     * 删除与指定键关联的条目。
     *
     * @param key 线程本地对象
     */
    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;
            }
        }
    }

    /**
     * 替换遇到的过期条目,用指定键的条目。
     * 不管是否已经存在指定键的条目,都会将value参数存储在条目中。
     * 作为副作用,此方法清除运行中的所有过期条目。
     *
     * @param key 键
     * @param value 要与键关联的值
     * @param 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();

            // 如果找到键,需要将其与过期条目交换,以保持哈希表顺序。
            // 然后可以将新的过期槽,或者在它上面遇到的任何其他过期槽,
            // 发送给expungeStaleEntry来删除或重新散列运行中的所有其他条目。
            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);
    }

    /**
     * 通过重新散列位于staleSlot和下一个空槽之间的可能冲突条目,来删除过期条目。
     * 这也会清除遇到的所有其他过期条目。参考Knuth的第6.4节。
     *
     * @param staleSlot 已知具有空键的槽的索引
     * @return 在staleSlot之后的下一个空槽的索引(在staleSlot和该槽之间的所有槽都将被检查并清理)
     */
    private int expungeStaleEntry(int staleSlot) {
        Entry[] tab = table;
        int len = tab.length;

        // 清除staleSlot处的条目
        tab[staleSlot].value = null;
        tab[staleSlot] = null;
        size--;

        // 重新散列,直到遇到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;

                    // 不同于Knuth的6.4算法R,我们必须扫描到null,
                    // 因为可能存在多个过期条目。
                    while (tab[h] != null)
                        h = nextIndex(h, len);
                    tab[h] = e;
                }
            }
        }
        return i;
    }

    /**
     * 启发式地扫描一些单元格,寻找过期条目。
     * 这在添加新元素或已经删除另一个过期条目时被调用。它执行对数数量的扫描,
     * 作为不进行扫描(快速但保留垃圾)和与元素数量成比例的扫描次数之间的平衡,
     * 后者将导致某些插入需要O(n)时间。
     *
     * @param i 已知不包含过期条目的位置,从i之后的元素开始扫描
     * @param n 扫描控制:扫描log2(n)个单元格,除非找到过期条目,
     *          在这种情况下,还要额外扫描log2(table.length)-1个单元格。
     *          当从插入操作中调用时,此参数是元素数量;当从replaceStaleEntry中调用时,此参数是表的长度。
     *          (注意:可以通过对n进行加权而不是仅使用log n来改变此行为,但这个版本简单、快速,并且似乎工作得很好。)
     *
     * @return 如果删除了任何过期条目,则返回true
     */
    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;
    }

    /**
     * 扩展表中的所有过期条目。
     */
    private void expungeStaleEntries() {
        Entry[] tab = table;
        int len = tab.length;
        for (int j = 0; j < len; j++) {
            Entry e = tab[j];
            if (e != null && e.get() == null)
                expungeStaleEntry(j);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值