ThreadLocal 详解(源码解析)

目录

1. ThreadLocal 基本使用

2. ThreadLocal 数据结构

3. ThreadLocal.set()

3.1. ThreadLocalMap 初始化设置

3.2. 非初始化设置

3.3. replaceStaleEntry() -- 替换过期数据

4. ThreadLocal.get()

4.1. 图解

4.2. 源码

5. ThreadLocal 中的 Hash

5.1. Hash 算法

5.2. Hash 冲突

6. 清除方式

6.1. 探测式清除

6.2. 启发式清除

7. ThreadLocal 扩容机制

7.1. 探测式清理

7.2. reseize()

8. ThreadLocal 常见问题

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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值