ThreadLocal 全解

threadLocalHashCode

被修饰符 final 修饰,一经初始化不能被修改,调用 nextHashCode 方法初始化。

在计算当前 ThreadLocal 对象在 ThreadLocalMap 类的 Entry[] 类型的 table 域中的索引时被使用。

private final int threadLocalHashCode = nextHashCode();

nextHashCode

被修饰符 static 修饰,在多个 ThreadLocal 对象之间共享,是 AtomicInteger 类的实例。

只在 nextHashCode 方法中被使用。

private static AtomicInteger nextHashCode = new AtomicInteger();

HASH_INCREMENT

斐波那契哈希常量,使由 threadLocalHashCode & (length - 1) 计算出来的索引分布均匀,所以 ThreadLocal 类选择开放地址法来解决哈希冲突。

threadLocalHashCode

0x61c88647

0xc3910c8e

0x255992d5

0x8722191c

0xe8ea9f63

0x4ab325aa

0xac7babf1

0xe443238

0x700cb87f

threadLocalHashCode & (length - 1)

7

14

5

12

3

10

1

8

15

private static final int HASH_INCREMENT = 0x61c88647;

nextHashCode()

nextHashCode 域是 AtomicInteger 类的实例,它的 getAndAdd 方法原子地将 HASH_INCREMENT(0x61c88647) 添加到当前值,底层是 Unsafe 对象的 getAndAddInt 方法,线程安全。

只在 final 类型的 threadLocalHashCode 域初始化时被调用。

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

initialValue()

返回空。在 SuppliedThreadLocal 子类中被重写,返回 supplier 域的 get 方法的返回值。也可以使用匿名内部类重写。

只在 setInitialValue 方法中被调用。

protected T initialValue() {
    return null;
}

withInitial(Supplier)

创建初始值由 supplier 参数的 get 方法确定的 ThreadLocal 类的实例,supplier 参数不能为空。

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

ThreadLocal()

public ThreadLocal() {
}

get()

获得当前线程 ThreadLocalMap 类型的 threadLocals 域,如果 threadLocals 域不为空,获得 threadLocals 域中以当前 ThreadLocal 对象为“键”的 Entry 对象,如果 Entry 对象不为空,返回 Entry 对象的 value 域;如果 threadLocals 域或 Entry 对象至少有一个为空,返回 setInitialValue 方法的返回值。

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

setInitialValue()

获得当前线程 ThreadLocalMap 类型的 threadLocals 域,将“键值对” 放入 threadLocals 域中,如果 threadLocals 域为空,初始化 threadLocals 域,最后返回局部变量 value,局部变量 value 在 ThreadLocal 类和 InheritableThreadLocal 类中都为空,可以使用 withInitial 方法创建初始值由 supplier 参数的 get 方法确定的 ThreadLocal 类的实例,也可以使用匿名内部类重写 initialValue 方法从而修改局部变量 value。

只在 set 方法中被调用。

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

set(T)

与 setInitialValue 方法大同小异,只是局部变量 value 变为参数 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);
}

remove()

获得当前线程 ThreadLocalMap 类型的 threadLocals 域,如果 threadLocals 域不为空,将与当前 ThreadLocal 对象关联的 Entry 对象从 threadLocals 域中移除。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

getMap(Thread)

返回 Thread 类型参数的 ThreadLocalMap 类型的 threadLocals 域。

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

createMap(Thread, T)

初始化 threadLocals 域,并将“键值对” 放入 threadLocals 域中。

在 setInitialValue 方法和 set 方法中被调用。

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

createInheritedMap(ThreadLocalMap)

将参数 parentmap 传递给构造方法 ThreadLocalMap(ThreadLocalMap) 并返回构造方法创建的 ThreadLocalMap 对象。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

childValue(T)

抛出 UnsupportedOperationException 异常,在本类的子类中重写后才会有意义。

只在 ThreadLocalMap(ThreadLocalMap) 构造方法中被调用。

T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

SuppliedThreadLocal

静态内部类,不能被继承,是 ThreadLocal 类的子类。

static final class SuppliedThreadLocal<T> extends ThreadLocal<T>

supplier

一经初始化不能被修改,在构造方法 SuppliedThreadLocal(Supplier) 中被初始化。

private final Supplier<? extends T> supplier;

SuppliedThreadLocal(Supplier supplier)

初始化 supplier 域,要求参数 supplier 不为空。

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

initialValue()

返回 supplier 域的 get 方法的返回值。

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

ThreadLocalMap

静态内部类。

static class ThreadLocalMap

Entry

ThreadLocalMap 类的静态内部类,是 WeakReference> 类的子类。

static class Entry extends WeakReference<ThreadLocal<?>>

value

与 ThreadLocal 对象关联的值。

Object value;

Entry(ThreadLocal, Object)

super(k) -> WeakReference(T) -> Reference(T) -> Reference(T, ReferenceQueue) -> Reference(k, null),初始化 value 域。

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

INITIAL_CAPACITY

最初的容量常量,Entry[] 类型的 table 域初始化时的长度,一定是 2 的幂。

private static final int INITIAL_CAPACITY = 16;

table

存储以 ThreadLocal 对象为“键”的“键值对”,必要时长度扩容为原来的两倍,长度一定是 2 的幂。

private Entry[] table;

size

table 域中 Entry 对象的数目。

private int size = 0;

threshold

阈值,为 table 域长度的三分之二,和 HashMap 类的 threshold 相似。

private int threshold; // Default to 0

setThreshold(int)

将 threshold 域赋值为参数 len 的三分之二。

在 ThreadLocalMap(ThreadLocal, Object),ThreadLocal(ThreadLocal) 和 resize 方法中被调用。

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

nextIndex(int, int)

获得 table 域中索引 i 的下一个索引,把 table 域看作是一个环形数组。

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

prevIndex(int, int)

获得 table 域中索引 i 的上一个索引,把 table 域看作是一个环形数组。

private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

ThreadLocalMap(ThreadLocal, Object)

初始化 table 域,长度为常量 INITIAL_CAPACITY,计算 ThreadLocal 类型的参数 firstkey 在 table 域中的索引并用参数 firstKey 和 firstValue 初始化该索引的位置,将阈值赋值为 INITIAL_CAPACITY 的三分之二。ThreadLocalMap 对象是懒创建的,用到的时候才会被创建。

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

ThreadLocalMap(ThreadLocalMap)

初始化 threshold 域和 table 域,threshold 域为 ThreadLocalMap 类型的参数 parentMap 的 table 域的长度的三分之二,table 域为参数 parentMap 的 table 域的浅拷贝。实现了父子线程局部变量的传递,但是之后父子线程的局部变量除了 ThreadLocal 对象变为 null 的情况外互不影响。

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

getEntry(ThreadLocal)

获得与 key 关联的 Entry 对象,如果 Entry 对象不为空且与 Entry 对象的关联的 ThreadLocal 对象与参数 key 引用的 ThreadLocal 对象相同,返回 Entry 对象,最大限度地提高直接命中的性能;否则,返回 getEntryAfterMiss 方法的返回值。

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

getEntryAfterMiss(ThreadLocal, int, Entry)

如果 getEntry 方法传入的 Entry 对象为空,返回空;如果 Entry 对象不为空,

如果与 Entry 对象关联的 ThreadLocal 对象与参数 key 引用的 ThreadLocal 对象相同,返回 Entry 对象;如果与 Entry 对象关联的 ThreadLocal 对象为空,调用 expungeStaleEntry 方法,对 table 域中索引为 expungeStaleEntry 方法的返回值的 Entry 对象进行判断;否则,对 table 域中索引 i 位置的下一个位置的 Entry 对象进行判断。如果 Entry 对象为空,返回空。

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

set(ThreadLocal key, Object value)

从由 ThreadLocal 类型的参数 key 的 threadLocalHashCode 域计算出来的索引开始,遍历 table 数组中的 Entry 对象直到遇到空的 Entry 对象,如果 Entry 对象关联的 ThreadLocal 对象与参数 key 引用的 ThreadLocal 对象相同,将 Entry 对象的 value 域赋值为参数 value,返回;如果 Entry 对象关联的 ThreadLocal 对象为空,调用 replaceStaleEntry 方法,将失效的 Entry 对象替换为指定“键”的 Entry 对象,返回;如果遇到了空的 Entry 对象,将其用参数 key 和 value 初始化,然后调用 cleanSomeSlots 方法,如果 cleanSomeSlots 的方法返回值为 false,即没有失效的 Entry 对象被删除,就判断 table 域中 Entry 对象的数目是否大于等于阈值,如果大于等于,调用 rehash 方法。

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

remove(ThreadLocal key)

遍历 table 域中的 Entry 对象,获得与 Entry 对象关联的 ThreadLocal 对象与参数 key 引用的 ThreadLocal 对象相同的 Entry 对象,将与这个 Entry 对象关联的 ThreadLocal 对象赋值为空,然后调用 expungeStaleEntry 方法,参数为这个 Entry 对象的索引。

只在 ThreadLocal 类的 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;
        }
    }
}

Reference.clear()

清除此引用对象。调用此方法不会导致此对象进入引用队列。此方法仅由 Java 代码调用;当垃圾回收器清除引用时,它直接执行此操作,而不调用此方法。

弱引用需要用 java.lang.ref.WeakReference 类来实现,它比软引用的生存期更短。对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存。

引用队列是用来配合引用工作的,没有引用队列一样可以运行。创建引用的时候可以指定关联的队列,当 GC 释放对象内存的时候,会将引用加入到引用队列,如果程序发现某个引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动,这相当于是一种通知机制。当关联的引用队列中有数据的时候,意味着引用指向的堆内存中的对象被回收。通过这种方式,JVM 允许我们在对象被销毁后做一些我们自己想做的事情。

0

public void clear() {
    this.referent = null;
}

replaceStaleEntry(ThreadLocal, Object, int)

获得 table 数组中 staleSlot 索引之前,空 Entry 对象之后的最前面的失效的 Entry 对象的索引,记为 slotToExpunge,即要删除的槽,如果没有找到符合条件的索引,slotToExpunge 变量的值为 staleSlot。

从 staleSlot 索引的下一个索引开始,遍历 table 数组中的 Entry 对象,直到 Entry 对象为空。如果 Entry 对象关联的 ThreadLocal 对象与参数 key 引用的 ThreadLocal 对象相同,更新 Entry 对象的 value 域,并交换当前位置和 staleSlot 位置的 Entry 对象,如果 slotToExpunge 的值和 staleSlot 的值相等,slotToExpunge 被赋值为当前索引,交换和赋值是为了后面调用 cleanSomeSlots 方法时,能够更彻底地删除 失效的 Entry 对象,返回;如果 Entry 对象关联的 ThreadLocal 对象为空,且 slotToExpunge 的值和 staleSlot 的值相等(即上一步没有找到符合条件的索引),slotToExpunge 的值变为当前索引。

如果遍历到空的 Entry 对象还没有与参数 key 的引用相同的 ThreadLocal 对象,将以参数 key 和 value 创建一个 Entry 对象放在 table 数组中 staleSlot 索引的位置。

最后判断 slotToExpunge 和 staleSlot 是否相等,如果相等,说明没有失效的 Entry 对象;如果不相等,调用 cleanSomeSlots 方法。

只在 set 方法中被调用。

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).
    int slotToExpunge = staleSlot;
    for (int i = prevIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;

    // Find either the key or trailing null slot of run, whichever
    // occurs first
    for (int i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        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;

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

            // Start expunge at preceding stale entry if it exists
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;
            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.
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // If key not found, put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // If there are any other stale entries in run, expunge them
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

expungeStaleEntry(int)

将 table 域中索引为 staleSlot 参数的 Entry 对象及其 value 域赋值为空,然后遍历它后面的 Entry 对象直到遇到空的 Entry 对象,获得与 Entry 对象关联的 ThreadLocal 对象,如果 ThreadLocal 对象为空,将 Entry 对象及其 value 域赋值为空;如果 ThreadLocal 对象不为空,将 Entry 对象重新放入 table 域中,如果 Entry 对象不在原来的位置了,将 table 域中原来的位置赋值为空。最后返回空 Entry 对象的索引。

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

cleanSomeSlots(int, int)

从索引为参数 i 的位置的下一个位置开始遍历 table 域中的 Entry 对象,如果在 set 方法中被调用,参数 n 为 table 域中 Entry 对象的数目;如果在 replaceStaleEntry 方法中被调用,参数 n 为 table 域的长度。参数 n 与循环的条件有关,如果 Entry 对象不为空且与它关联的 ThreadLocal 对象为空,参数 n 被赋值为 table 域的长度,并将当前索引作为参数调用 expungeStaleEntry 方法,下一个遍历的 Entry 对象的索引为 expungeStaleEntry 方法的返回值。如果有失效的 Entry 对象被删除,返回值为 true;否则,返回值为 false。参数 n 没有被赋值的情况下,循环执行 logn 次;参数 n 被赋值后,执行循环的次数由实际情况决定。

在 set 方法和 replaceStaleEntry 方法中被调用。

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

rehash()

批量删除失效的 Entry 对象。如果 table 域中 Entry 对象的数目大于等于 table 域长度的二分之一(len * 2 / 3 - len * 2 / 3 / 4),把 table 域的长度翻一番。

只在 set 方法中被调用。

private void rehash() {
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

resize()

创建一个类型为 Entry[] 的对象,其长度为 table 域长度的两倍。遍历 table 域中的 Entry 对象,如果 Entry 对象不为空,获得与 Entry 对象关联的 ThreadLocal 对象,

如果 ThreadLocal 对象为空,将与 ThreadLocal 关联的值赋值为空(为什么不将 Entry 对象也赋值为空呢?Entry 对象在 expungeStaleEntry 方法中被赋值为空);如果 ThreadLocal 对象不为空,将 Entry 对象放入新 Entry[] 对象中。最后更新 threshold 域、size 域和 table 域。

只在 rehash 方法中被调用。

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

expungeStaleEntries()

批量删除失效的 Entry 对象。

只在 rehash 方法中被调用。

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

上图中,实线代表强引用,虚线代表弱引用,如果 threadLocal 的强引用被置为空,threadLocal 不可达,在 GC 的时候会被回收,因此 entry 存在 referent 为空的情况,无法通过空的 referent 去获得 value,同时,存在引用链 thread 的引用 -> thread -> threadLocalMap -> entry -> value 的引用 -> value,导致 GC 的时候 value 可达不会被回收,这样就造成了内存泄漏。线程执行结束时 threadLocalMap 被赋值为空(Thread 类的 exit 方法),因此 threadLocal,threadLocalMap 和 entry 都会被回收,但是我们会使用线程池去维护线程,为了复用线程是不会结束的,所以内存泄漏就值得我们关注。

如果 Entry 为强引用,threadLocal 的引用被赋值为空,threadLocal 仍可达,不会被垃圾回收,需要手动回收;Entry 为弱引用,虽然会内存泄漏,但是在 threadLocal 的生命周期中,都会删除 referent 为空的无效的 entry,尽可能地保证内存不泄漏。

每次用完 ThreadLocal,调用它的 remove 方法。如果使用线程池,没有及时清理 ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。

参考

https://www.jianshu.com/p/dde92ec37bd1

https://www.jianshu.com/p/3c5d7f09dfbd

https://blog.csdn.net/a837199685/article/details/52712547

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值