Java 多线程关键字 ThreadLocal 学习

本文深入探讨Java的ThreadLocal关键字,解析其如何为每个线程提供独立的变量副本,避免并发冲突。通过分析ThreadLocal的成员变量、set和get方法,以及其在线程池中的应用,揭示弱引用在防止内存泄漏中的作用,并建议将ThreadLocal声明为static以优化内存使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基于 JDK 1.8

概述

ThreadLocal,顾名思义线程本地变量,即每个线程的内部,都有存有这个变量的副本,多个线程之间不可见,从而避免了对共享变量的操作,而造成的并发问题。

本文先对 ThreadLocal 进行全局的了解,ThreadLocalMap 的源码放到后面的文章中进行分析学习。

  • 使用

通过 set 方法赋值,或者初始化的时候重写 initialValue 方法

private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
    @Override
    protected Integer initialValue() {
        return 1;
    }
};

ThreadLocal 源码分析

在这里插入图片描述

  • 每个 Thread 对象里面包含一个 ThreadLocal.ThreadLocalMap 属性,所以不同线程的这个 ThreadLocalMap 属性互不可见
  • 每个线程的 ThreadLocalMap 互相不可见,因为这是线程自己的属性
  • ThreadLocalMap 里面包含多个 Entry,Entry 的 key 通过弱引用指向 ThreadLocal 对象,value 为初始化设定的值

成员变量

threadLocalHashCode 用于表示 ThreadLocal 对象对应的 hash 值,每创建一个 ThreadLocal 对象,该对象的 threadLocalHashCode 值就会累加 HASH_INCREMENT 。

0x61c88647对应十进制=1640531527,(根号5-1)*2的31次方,转换成long类型就是2654435769,转换成int类型就是-1640531527,可见 HASH_INCREMENT 与黄金分割比例相关,这样的实现方式能够形成较好的散列效果。

文档中解释到这种计算散列值的方式很特殊,仅在 ThreadLocalMaps 中使用效果比较好。


private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c88647;

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

成员方法 set

每个 Thread 都有自己的 ThreadLocalMap,Map 的 key 就是当前线程的 ThreadLocal,value 就是你要定义的 ThreadLocal 的值

public void set(T value) {
    Thread t = Thread.currentThread();  // 获取当前线程对象
    ThreadLocalMap map = getMap(t); // 获取该线程的 ThreadLocalMap 
    if (map != null) // 存在直接赋值
        map.set(this, value);
    else // 不存在创建一个 ThreadLocalMap,key 为当前线程,value 为设置的值
        createMap(t, value);
}

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

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

成员方法 get

首先获取当前线程的 ThreadLocalMap,根据 key 找到 Map 中的 Entry ,返回 entry 的 value;若 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();
}

private T setInitialValue() {
    T value = initialValue();  // 返回 null,或者返回重写了 initialValue 的方法
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);  // 设置 key = threadlocal value = null
    else
        createMap(t, value);  // 初始化 map
    return value;
}

protected T initialValue() {
    return null;
}

remove

从 Map 中移除这个 entry

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

其它成员方法

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

// 仅在继承于 ThreadLocal 的子类可见
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

ThreadLocal 与线程池

ThreadLocal 变量与线程池使用,线程池存在线程复用的情况,如果前一个线程 set 了 ThreadLocal 变量,后面重复使用的时候可能会取到前面的值。

故不建议两者组合使用,非要使用,建议先调用 remove 方法

public static void main(String[] args) throws InterruptedException {

    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    ExecutorService executorService = new ThreadPoolExecutor(1, 1, 5000, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(100), new AbortPolicy());

    executorService.execute( ()->{
        System.out.println(Thread.currentThread().getName() + " set");
        threadLocal.set("5");
    });

    Thread.sleep(5000);

    executorService.execute( () ->{
        System.out.println(Thread.currentThread().getName() + " get");
        System.out.println(threadLocal.get());
    });
}

输出:后面执行的方法获取到前面方法设定的值
pool-1-thread-1 set
pool-1-thread-1 get
5

为什么 ThreadLocalMap 使用弱引用存储 ThreadLocal

在这里插入图片描述
图中的连线 5 就是弱引用

若 ThreadLocalMap 对堆中的 ThreadLocal 对象是强引用,那么当栈中的 ThreadLocal ref 被清除的时候,此时堆中的 ThreadLocal 对象由于还被 Map 强引用,导致无法被回收。

若使用弱引用,那么栈中的 threadlocal ref 被清除时,ThreadLocal 对象只被 Map 弱引用所关联。根据垃圾回收规则,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象,此时内存可以被释放。

当上面的 ThreadLocal 对象被清除之后,此时 Entry 数组就会存在 key = null,value != null 的节点,为了进一步回收这部分内存,Map 的 set,get,remove 方法的执行,会去删除 key = null 的 Entry 节点,所以 entry 节点最终也会被回收(可能不会那么及时)。

所以为了减少 ThreadLocal 造成的内存泄漏问题,建议:

分析ThreadLocal的弱引用与内存泄漏问题-Java8

为什么 ThreadLocal 建议声明为 static

好处:避免重复创建TSO(Thread Specific Object,即 ThreadLocal 所关联的对象)所导致的内存浪费。

参考

ThreadLocal源码深度剖析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值