目录
3.3. replaceStaleEntry() -- 替换过期数据
8.1. 为什么 ThreadLocal 的 value 不设置为弱引用
1. ThreadLocal 基本使用
public class ThreadLocalTest {
/**
* 创建一个 ThreadLocal 实例,用来存储每个线程的独立变量
*/
private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
// 创建并启动多个线程
for (int i = 0; i < 3; i++) {
new Thread(new Worker(i)).start();
}
}
static class Worker implements Runnable {
private final int threadId;
Worker(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
// 设置当前线程的 ThreadLocal 变量值
threadLocal.set(threadId);
// 模拟工作流程
try {
// 模拟处理时间
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 获取并打印当前线程的 ThreadLocal 变量值
System.out.println("Thread " + threadId + " value: " + threadLocal.get());
// 清除当前线程的 ThreadLocal 变量值(可选)
threadLocal.remove();
}
}
}
输出的结果:
Thread 1 value: 1
Thread 2 value: 2
Thread 0 value: 0
由此可以看出,在不同线程中 ThreadLocal 可以做到线程隔离的效果
2. ThreadLocal 数据结构
Thread 类中有一个类型为 ThreadLocal.ThreadLocalMap 的实例变量 threadLocals,这意味着每个线程都拥有一个独立的 ThreadLocalMap。
ThreadLocal 本身并不直接存储任何值,而是用来作为一个映射,将每个线程的局部变量与该线程进行关联。当一个线程调用 ThreadLocal 的set()或 get()方法时,实际上是在访问该线程的 ThreadLocal.ThreadLocalMap 实例
ThreadLocalMap 是 ThreadLocal 的静态内部类,它维护了一个 Entry 数组。
- 这个数组的结构类似于 HashMap,但不使用链表。
- 数组中的每个 Entry 都有一个键值对,其中键是 ThreadLocal 对象的弱引用,值是线程的局部变量。

3. ThreadLocal.set()
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程获取 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 验证是否为空,不为空直接插入,为空则需要创建 ThreadLocalMap
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
/**
* 根据当前的 Thread 创建 ThreadLocalMap 并赋予初始值 firstValue
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
3.1. ThreadLocalMap 初始化设置
private static final int INITIAL_CAPACITY = 16;
/**
* 初始化ThreadLocalMap
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化 table,默认长度为 16
// Entry 是一个内部类,类似于 Map.Entry,存储键值对
table = new Entry[INITIAL_CAPACITY];
// 使用 ThreadLocal 的哈希码计算下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 创建一个新的 Entry 对象,将 firstKey 和 firstValue 存储在 table 的计算位置 i 上
// (INITIAL_CAPACITY - 1) 相当于一个位掩码,用于在表的范围内进行取模运算
table[i] = new Entry(firstKey, firstValue);
// 调整容量
size = 1;
// 调整负载系数,将阈值设置为容量的 2/3,这个阈值用于判断何时需要扩容
setThreshold(INITIAL_CAPACITY);
}
/**
* 设置扩容阈值
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
3.2. 非初始化设置
/**
* 在 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)]) {
// 获取当前 Entry 的键
ThreadLocal<?> k = e.get();
// 如果找到相同的键,则更新值
if (k == key) {
e.value = value;
return;
}
// 当前的 key = null,则表示虚引用的 ThreadLocal 是被 GC 回收的状态
if (k == null) {
// 替换过期数据
replaceStaleEntry(key, value, i);
return;
}
}
// 走到这里就说明下标为 i 的位置上,是没有元素的,所以可以直接将新建的 Entry 元素插入到 i 这个位置
tab[i] = new Entry(key, value);
int sz = ++size;
// 调用 cleanSomeSlots() 做一次启发式清理工作,清理散列数组中Entry的key过期的数据
// 如果清理工作完成后,未清理到任何数据,且 size 超过了阈值(数组长度的2/3),进行 rehash() 操作
if (!cleanSomeSlots(i, sz) && sz >= th

最低0.47元/天 解锁文章
1028

被折叠的 条评论
为什么被折叠?



