实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

2025-04-28 0 519

目录

一、ThreadLocal基本知识回顾分析

(一)ThreadLocal原理

(二)既然ThreadLocalMap的key是弱引用,GC之后key是否为null?

(三)ThreadLocal中的内存泄漏问题及JDK处理方法

(四)部分核心源码回顾

ThreadLocal.set()方法源码详解

ThreadLocalMap.get()方法详解

ThreadLocal.remove()方法源码详解

(五)简单的直观体会

二、基于Threadlocal实现的上下文管理组件ContextManager

(一)定义 ContextManager 类

(二)使用 ContextManager 进行上下文管理

(三)扩展 ContextManager 的使用方式

三、在线程池中传递ContextManager

(一)增加静态方法,用于在已有的上下文中执行任务

(二)自定义线程池实现

(三)测试自定义线程池

四、总结


干货分享,感谢您的阅读!

探讨如何基于 ThreadLocal 实现一个高效的上下文管理组件,以解决多线程环境下的数据共享和上下文管理这些问题。通过具体的代码示例和实战展示 ThreadLocal 如何为多线程编程提供一种简洁而高效的上下文管理方案。

一、ThreadLocal基本知识回顾分析

(一)ThreadLocal原理

ThreadLocal 是 Java 提供的一个用于线程级别数据存储的类。它为每个线程提供了独立的变量副本,使得每个线程都能独立地操作自己的变量,而不会与其他线程的变量冲突。这种机制特别适用于需要线程隔离的场景,通过 ThreadLocal,我们可以确保同一个变量在不同线程中拥有各自独立的值。

我们先来看下Thread、ThreadLocalMap、ThreadLocal结构关系:

  • 每个Thread都有一个ThreadLocalMap变量
  • ThreadLocalMap内部定义了Entry(ThreadLocal<?> k, Object v)节点类,这个节点继承了WeakReference类泛型为ThreacLocal

ThreadLocal主要作用就是实现线程间变量隔离,对于一个变量,每个线程维护一个自己的实例,防止多线程环境下的资源竞争,那ThreadLocal是如何实现这一特性的呢?基本原理实现如下:

  1. 每个Thread对象中都包含一个ThreadLocal.ThreadLocalMap类型的threadlocals成员变量;

  2. 该map对应的每个元素Entry对象中:key是ThreadLocal对象的弱引用,value是该threadlocal变量在当前线程中的对应的变量实体;

  3. 当某一线程执行获取该ThreadLocal对象对应的变量时,首先从当前线程对象中获取对应的threadlocals哈希表,再以该ThreadLocal对象为key查询哈希表中对应的value;

  4. 由于每个线程独占一个threadlocals哈希表,因此线程间ThreadLocal对象对应的变量实体也是独占的,不存在竞争问题,也就避免了多线程问题。

(二)既然ThreadLocalMapkey是弱引用,GC之后key是否为null

在搞清楚这个问题之前,我们需要先搞清楚Java的四种引用类型

  • 强引用:new出来的对象就是强引用,只要强引用存在,垃圾回收器就永远不会回收被引用的对象,哪怕内存不足的时候。
  • 软引用:使用SoftReference修饰的对象被称为软引用,在内存要溢出的时候软引用指向的对象会被回收。
  • 弱引用:使用WeakReference修饰的对象被称为弱引用,只要发生垃圾回收,被弱引用指向的对象就会被回收。
  • 虚引用:虚引用是最弱的引用,用PhantomReference进行定。唯一的作用就是用来队列接受对象即将死亡的通知。

这个问题的答案是不为null,从上图的图示就可以直接看出。

(三)ThreadLocal中的内存泄漏问题及JDK处理方法

由图可知,ThreadLocal.ThreadLocalMap 对应的Entry中,key为ThreadLocal对象的弱引用,方法执行对应栈帧中的ThreadLocal引用为强引用。当方法执行过程中,由于栈帧销毁或者主动释放等原因,释放了ThreadLocal对象的强引用,即表示该ThreadLocal对象可以被回收了。又因为Entry中key为ThreadLocal对象的弱引用,所以当jvm执行GC操作时是能够回收该ThreadLocal对象的。

Entry中value对应的是变量实体对象的强引用,因此释放一个ThreadLocal对象,是无法释放ThreadLocal.ThreadLocalMap中对应的value对象的,也就造成了内存泄漏。除非释放当前线程对象,这样整个threadlocals都被回收了。但是日常开发中会经常使用线程池等线程池化技术,释放线程对象的条件往往无法达到。

JDK处理的方法是,在ThreadLocalMap进行set()get()remove()的时候,都会进行清理:

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);
}
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)
            //如果key为null,对应的threadlocal对象已经被回收,清理该Entry
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

(四)部分核心源码回顾

ThreadLocalAPI很少就包含了4个,分别是get()set()remove()withInitial(),源码如下:

public T get() {}

public void set(T value){}

public void remove(){}

public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        
}
  • get():从当前线程的 ThreadLocalMap 获取与当前 ThreadLocal 对象对应的值。如果 ThreadLocalMap 中不存在该值,则调用 setInitialValue() 方法进行初始化。
  • set(T value):将当前线程的 ThreadLocalMap 中的值设置为给定的 value。如果当前线程没有 ThreadLocalMap,则会创建一个新的 ThreadLocalMap 并将值设置进去。
  • remove():从当前线程的 ThreadLocalMap 中移除与当前 ThreadLocal 对象对应的值,帮助防止内存泄漏。
  • withInitial(Supplier<? extends T> supplier):返回一个新的 ThreadLocal 对象,其初始值由 Supplier 提供。这允许使用者在创建 ThreadLocal 时指定初始值。

针对这几个源码我们重点进行分析和体会。

ThreadLocal.set()方法源码详解

pubic void set(T value) {
    // 获取当前线程
    Thread t = Threac.currentThread();
    // 获取当前线程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // 如果map不为null, 调用ThreadLocalMap.set()方法设置值
    if (map != null)
        map.set(this, value);
    else 
        // map为null,调用createMap()方法初始化创建map
        createMap(t, value);
}

// 返回线程的ThreadLocalMap.threadLocals
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 调用ThreadLocalMap构造方法创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap

平台声明:以上文章转载于《CSDN》,文章全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,仅作参考。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/xiaofeng10330111/article/details/139667074

遇见资源网 JAVA 实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理 http://www.ox520.com/157715.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务