首页 > 其他分享 >ThreadLocal 释放的方式有哪些

ThreadLocal 释放的方式有哪些

时间:2024-09-11 19:21:24浏览次数:3  
标签:释放 变量 哪些 ThreadLocal threadLocals 线程 null public

ThreadLocal基础概念:IT-BLOG-CN

ThreadLocalJava中用于在同一个线程中存储和隔离变量的一种机制。通常情况下,我们使用ThreadLocal来存储线程独有的变量,并在任务完成后通过remove方法清理这些变量,以防止内存泄漏。然而,在使用线程池时,线程会被重用,这可能导致ThreadLocal变量未被及时清理,从而引发内存泄漏问题。

除了直接调用ThreadLocalremove方法外,还有一些其他方式可以帮助释放ThreadLocal变量:

一、在线程池中使用自定义的ThreadFactory

创建一个自定义的ThreadFactory,在创建线程时添加钩子,以便在任务完成后清理ThreadLocal变量。扩展:搭建统一线程池平台,对该部分进行了改造。提供多个工厂,就包含自动清理工厂。

import java.util.concurrent.ThreadFactory;

public class CleaningThreadFactory implements ThreadFactory {
    private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();

    @Override
    public Thread newThread(Runnable r) {
        return defaultFactory.newThread(() -> {
            try {
                r.run();
            } finally {
                // 清理ThreadLocal变量
                ThreadLocalHolder.clear();
            }
        });
    }
}

这里的ThreadLocalHolder就是所有ThreadLocal的一个管理类,这里举个例子:

public class ThreadLocalHolder {
private static final ThreadLocal<AggAlibabaRerQueryResponse> TL_AGG_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<OpenAlibabaSearchResponse> TL_ORDER_DETAIL = new ThreadLocal<>();

// get/set 只流一个参考
public static void setAggRefRer(AggAlibabaRerQueryResponse aggRefRer) {
TL_AGG_REF_RER.set(aggRefRer);
}

public static FlightRefRerQueryResponse getFlightRefRer() {
    return TL_FLIGHT_REF_RER.get();
}


/**
 * 用于清空threadlocal,否则会有内存泄漏
 */
 public static void clear() {
    TL_AGG_REF_RER.remove();
    TL_ORDER_DETAIL.remove();
}

二、使用ThreadPoolExecutor的钩子方法

可以扩展ThreadPoolExecutor并覆盖其beforeExecuteafterExecute方法,以便在任务执行前后进行清理操作。

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;

public class CleaningThreadPoolExecutor extends ThreadPoolExecutor {
    public CleaningThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, LinkedBlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        super.beforeExecute(t, r);
        // 清理之前的ThreadLocal变量
        ThreadLocalHolder.clear();
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);
        // 清理当前的ThreadLocal变量
        ThreadLocalHolder.clear();
    }
}

三、使用装饰器模式包装RunnableCallable

可以创建一个装饰器,包装RunnableCallable任务,在任务执行前后进行清理操作。

import java.util.concurrent.Callable;

public class CleaningRunnable implements Runnable {
    private final Runnable task;

    public CleaningRunnable(Runnable task) {
        this.task = task;
    }

    @Override
    public void run() {
        try {
            task.run();
        } finally {
            // 清理ThreadLocal变量
            ThreadLocalHolder.clear();
        }
    }
}

public class CleaningCallable<V> implements Callable<V> {
    private final Callable<V> task;

    public CleaningCallable(Callable<V> task) {
        this.task = task;
    }

    @Override
    public V call() throws Exception {
        try {
            return task.call();
        } finally {
            // 清理ThreadLocal变量
            ThreadLocalHolder.clear();
        }
    }
}

四、使用ThreadLocal的子类

可以创建一个ThreadLocal的子类,并在任务完成后自动清理变量。可以通过覆盖initialValue方法来实现:finalize 出发的时机是在gc的时候,但是finalize方法在现代Java开发中并不推荐使用,因为它的执行时间和执行顺序是不确定的。

public class AutoCleanupThreadLocal<T> extends ThreadLocal<T> {
    @Override
    protected void finalize() throws Throwable {
        this.remove();
        super.finalize();
    }
}

五、TheadLocal 实际使用案例

将整个流程中需要用到的接口数据都存储起来,这个流程中调用链路比较深,同时也存在并发的操作,可以使用ThreadLocal

public class ThreadLocalHolder {
private static final ThreadLocal<AggAlibabaRerQueryResponse> TL_AGG_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<OpenAlibabaSearchResponse> TL_ORDER_DETAIL = new ThreadLocal<>();
private static final ThreadLocal<FlightAlibabaResponse> TL_FLIGHT_REF_RER = new ThreadLocal<>();
private static final ThreadLocal<XOrderAlibabaInfo> TL_X_ORDER_DETAIL = new ThreadLocal<>();
private static final ThreadLocal<FlightAlibabaResponseBodyType> TL_DOM_FLIGHT_SEARCH_RESULT = new ThreadLocal<>();
private static final ThreadLocal<ResponseAlibabaType> TL_RESCHEDULE_FLIGHT_SEARCH_RESULT = new ThreadLocal<>();

// get/set 只流一个参考
public static void setAggRefRer(AggAlibabaRerQueryResponse aggRefRer) {
TL_AGG_REF_RER.set(aggRefRer);
}

public static FlightRefRerQueryResponse getFlightRefRer() {
    return TL_FLIGHT_REF_RER.get();
}


/**
 * 用于清空threadlocal,否则会有内存泄漏
 */
 public static void clear() {
    TL_AGG_REF_RER.remove();
    TL_ORDER_DETAIL.remove();
    TL_FLIGHT_REF_RER.remove();
    TL_X_ORDER_DETAIL.remove();
    TL_DOM_FLIGHT_SEARCH_RESULT.remove();
    TL_RESCHEDULE_FLIGHT_SEARCH_RESULT.remove();
}

六、基础支持补充 ----- ThreadLocal 的实现原理

下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocalsinheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部类ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

只有当线程第一次调用ThreadLocalset或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。下面我们通过查看ThreadLocalsetget以及remove方法来查看ThreadLocal具体实怎样工作的。

【1】set方法源码

public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}

在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下:

ThreadLocalMap getMap(Thread t) {
   return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建 threadLocals,该方法如下所示:createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

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

【2】get方法源码:get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

【3】remove方法的实现: remove方法判断当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量。

public void remove() {
    //获取当前线程绑定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
     if (m != null)
         m.remove(this);
}

private void remove(ThreadLocal<?> key) {
    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)]) {
        if (e.refersTo(key)) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

【4】如下图所示: 每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(可能会导致内存溢出),因此使用完毕需要将其remove掉。

标签:释放,变量,哪些,ThreadLocal,threadLocals,线程,null,public
From: https://blog.csdn.net/zhengzhaoyang122/article/details/142006996

相关文章

  • 电子邮件加密软件有哪些?推荐5款加密软件app,速速来看!
    如果你还没有安装电子邮件加密软件,那么,你的邮件数据可能正面临被窃取的风险。今天,就为大家推荐5款优秀的电子邮件加密软件app,为你的信息安全保驾护航!第一款:域智盾域智盾软件是一款专为保护企业通信安全而设计的专业工具,它采用先进的加密算法和多种功能,为企业提供了一个全方......
  • 数据资产入表全流程解析,助力企业数据要素价值释放
    数据资产入表即数据资产会计核算,指的是把有价值的数据编制进资产负债表,作为企业沉淀的无形资产,让数据要素的交易流通变得合规,数据价值可计算。2023年8月21日,财政部发布《企业数据资源相关会计处理暂行规定》,并于2024年1月1日开始实施,首次将数据资源纳入企业会计核算体系,明确了数......
  • 【话费充值】话费API接口对接有哪些关键步骤
    话费API接口对接通常包括以下几个关键步骤:选择服务提供商:选择一个可靠的话费充值API服务提供商,这可能是电信运营商本身或是一个信誉良好的第三方服务提供商。注册和认证:在选定的服务提供商平台上注册,并获得API访问权限。这通常涉及到创建一个开发者账号,并获取API密钥或其他形......
  • 盲盒小程序开发对市场发展有哪些积极作用?
    盲盒作为近几年备受大众关注的潮玩方式,市场规模持续扩大!盲盒小程序在互联网的发展影响下,也逐渐提高了自身的优势,为盲盒市场带来了更多的趣味性和新鲜活力。小程序作为一种便利的推广、购买模式,在盲盒市场中将发挥着重要的作用,从而提高盲盒市场的吸引力,推动市场发展。盲盒小......
  • 清理C盘缓存,你知道清理C盘缓存有哪些方法吗
    清理C盘缓存是维护Windows系统性能的重要步骤之一。以下是一些常用的清理C盘缓存的方法:一、使用Windows内置工具磁盘清理打开“设置”应用,选择“系统”>“存储”。在“存储”页面中,找到并点击“C盘”的详细信息。点击“临时文件”,系统将列出可清理的临时文件,如系统更新文......
  • 转行人员想做网络安全工程师有哪些要求?
    ......
  • ThreadLocal线程重用时带来的问题
    背景我们都知道ThreadLocal实现了资源在线程内独享,线程之间隔离。实际使用中,ThreadLocal适用于变量在线程间隔离,而在方法或类间共享的场景。比如用户信息,当用户信息需要在多个方法之间传递或者共享使用的时候,同时,每个Tomcat请求的用户信息是私有的。这时可使用ThreadLocal,即直接......
  • 计算机及应用自考本科科目有哪些?
    自考本科的计算机及应用专业涉及的科目相当广泛,包括计算机基础理论、编程语言、数学基础、网络与数据通信以及软件工程等。该专业不仅注重理论知识的掌握,还强调实际应用能力的培养。以下是具体分析:基础理论科目计算机组成原理:涉及计算机的基本结构和工作原理,是学习其他计算......
  • 数据飞轮转进快递行业 能够为企业带来哪些新想象
    更多技术交流、求职机会,欢迎关注字节跳动数据平台微信公众号,回复【1】进入官方交流群 随着顺丰控股(002352.SZ)、中通快递(ZTO.N,2057.HK)、圆通速递(600233.SH)、申通快递(002468.SZ)、韵达股份(002120.SZ)、极兔速递(1519.HK)、德邦股份(603056.SH)7家快递公司陆续披露上半年业绩,国内快......
  • 小程序开发费用揭秘:影响微信小程序价格的因素有哪些?
    微信小程序的开发费用因具体需求和开发方式而异。以下是一些影响微信小程序开发费用的因素:功能和复杂度:微信小程序的功能和复杂度直接影响开发费用。简单的小程序可能只需要几千元,而复杂的小程序可能需要数万元或更高。设计和用户体验:微信小程序的设计和用户体验也会影响开......