ThreadLocal
- ThreadLocal 是什么?是做什么用的?
- ThreadLocal 实现原理是什么?
- ThreadLocalMap 是什么?
- 为什么ThreadLocalMap要使用弱引用?如何避免内存泄漏?
ThreadLocal 是什么?是做什么用的?
ThreadLocal 是一个 Java 类,它用来为每个线程提供一个唯一的副本,这个副本是对某个变量的引用。这个特性使得 ThreadLocal 中的变量对于每个线程来说都是独立的,即每个线程只能看到和修改自己的变量,而不能看到和修改其他线程的变量。
ThreadLocal 的主要用途是为每个线程提供一个唯一的变量副本,以便在多线程环境下实现数据的隔离和安全性。由于每个线程都有自己的变量副本,因此不会出现多个线程共享同一个变量的的情况,从而避免了线程安全问题。
ThreadLocal的使用场景包括:
- 在进行对象跨层传递的时候,使用 ThreadLocal 可以避免多次传递,打破层次间的约束。
- 线程间数据隔离。
- 进行事务操作,用于存储线程事务信息。
- 数据库连接,Session会话管理。
ThreadLocal 实现原理是什么?
ThreadLocal 为每个线程提供一个唯一的副本。在不看源码之前,如果我们自己实现的话,很明显可以想到通过采用 Thread 类来实现,在T hread 类中定义一个 HashMap 用来存储, 在进行存储的时候, Key 和 Value 都需要自己去填写。并且而 ThreadLocal 大体上和我们实现方式差不多,只是没有采用 HashMap 进行存储,而是自己额外定义了 ThreadLocalMap 进行存储,并且 Key 也做了额外的设计

ThreadLocalMap 是什么?
ThreadLocalMap 是 ThreadLocal 类中的一个内部类,用于存储每个线程的 ThreadLocal 变量。具体来说,ThreadLocalMap 是一个 HashMap 对象,其中键为 ThreadLocal 对象,值为对应的 ThreadLocal 变量值。每个线程中都有一个自己的 ThreadLocalMap 类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
对 Entry 进行添加的时候,需要注意一点 ThreadLocalMap 采用的是(+1)开放地址法,不是拉链法。由于是拉链法,因此对 Entry 进行删除的时候需要注意,不能简单删除 Entry,还需要对后面的元素进行进行重哈希操作
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) { // 由于 ThreadLocalMap 的 key 是弱引用,因此可能存在 key 被回收,但是 value 没有被回收的情况,下面这个函数就是回收所有的 value
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0); // 循环数组,如果到了最后就返回到开始
}
/*
第一步,向前扫描,检查前面是否有 key == null 的元素,也就是 GC 留下的坑;这里用 slotToExpunge 表示第一个 key == null 的元素位置,如果没有,那么 slotToExpunge 就是当前位置的索引,也就是 staleSlot;
第二步,向后扫描,检查是否有 key == 当前 ThreadLocal 的元素。如果有,就将该元素放入到 staleSlot 位置,同时根据 slotToExpunge 的值,更新需要清理的元素位置,从 slotToExpunge 位置开始进行清理与 rehash,然后返回;
第三步,第二步扫描过程中最终遇到元素为 null,说明后面没有 key == 当前 ThreadLocal 的元素,停止扫描,更新 slotToExpunge 位置,然后从 slotToExpunge 位置开始,进行清理与 rehash 操作
*/
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);
}
为什么ThreadLocalMap要使用弱引用?如何避免内存泄漏?
使用弱引用有助于避免内存泄漏,因为垃圾回收器可以清除不再被引用的对象。如果 ThreadLocalMap 使用强引用,那么即使对象不再被其他引用使用,只要它被存储在 ThreadLocalMap 中,垃圾回收器就无法回收它,从而导致内存泄漏。
ThreadLocalMap 有一个名为 remove 的方法,用于清除当前线程中已经不存在的 Entry。这样可以确保 ThreadLocalMap 中只存储当前线程中有效的 Entry,避免因为条目无效而导致的内存泄漏问题。