当前位置: 首页 > news >正文

ThreadLocal 释放的方式有哪些

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();@Overridepublic 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);}@Overrideprotected void beforeExecute(Thread t, Runnable r) {super.beforeExecute(t, r);// 清理之前的ThreadLocal变量ThreadLocalHolder.clear();}@Overrideprotected 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;}@Overridepublic 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;}@Overridepublic 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> {@Overrideprotected 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值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//(4)如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(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值,去查找对应的线程变量,找到对应的mapThreadLocalMap map = getMap(t);//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值if (map != null)map.set(this, value);//如果map为null,说明首次添加,需要首先创建出对应的mapelsecreateMap(t, value);return value;
}

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

public void remove() {//获取当前线程绑定的threadLocalsThreadLocalMap 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 释放的方式有哪些

ThreadLocal基础概念&#xff1a;IT-BLOG-CN ThreadLocal是Java中用于在同一个线程中存储和隔离变量的一种机制。通常情况下&#xff0c;我们使用ThreadLocal来存储线程独有的变量&#xff0c;并在任务完成后通过remove方法清理这些变量&#xff0c;以防止内存泄漏。然而&…...

监控-zabbix

1运维监控 是指对计算机系统、网络、服务器等关键IT基础设施进行实时监控&#xff0c;以确保系统的稳定运行和及时发现潜在问题 2老监控框架&#xff08;不会用但需要知道&#xff09; Cacti&#xff1a; Cacti是一款基于PHP、MySQL开发的网络流量监测图形分析工具。主要监…...

设计模式 解释器模式(Interpreter Pattern)

文章目录 解释器模式简绍解释器模式的结构优缺点UML图具体代码实现Context 数据实体类&#xff0c;可以包含一些方法Abstract Expression 创建接口方法Terminal Expression 对数据简单处理Non-Terminal Expression 同样实现抽象接口方法Client&#xff08;客户端&#xff09; 调…...

Linux echo命令讲解及与重定向符搭配使用方法,tail命令及日志监听方式详解

echo echo具有回声&#xff0c;回响的意思&#xff0c;在linux系统中echo一般可以输出指定字符或用于命令执行 echo命令的用法为 echo 输出字符串 或 echo 命令 若参数为字符串则进行字符串输出&#xff0c;注意若字符串中含空格最好将其用引号括起&#xff0c;防止echo命…...

Linux网络:总结协议拓展

1. TCP/IP四层模型总结 2. 网络协议拓展 DNS协议&#xff08;地址解析协议&#xff09; TCP/IP使用IP地址和端口号来确定网络中一台主机的一个程序。 但是这样标定不方便记忆&#xff0c;于是开始引出主机名&#xff08;字符串&#xff09;&#xff0c;使用hosts文件来描述…...

去除恢复出厂设置中UI文字显示

文章目录 需求场景 一、代码跟踪与分析在线文字搜索RK平台本地源码搜索实际测试验证代码推理 二、实现方案三、延伸知识四、知识总结 需求 需求&#xff1a;去除恢复出厂设置中UI文字显示 场景 Android 相关产品各种方向旋转、强制横竖屏等需求&#xff0c;导致在恢复出厂设…...

《高校教育管理》

《高校教育管理》为中文社会科学引文索引&#xff08;CSSCI&#xff09;来源期刊、北大中文核心期刊、RCCSE中国核心学术期刊、人大“复印报刊资料”重要转载来源期刊&#xff0c;是江苏大学主办&#xff0c;中国高等教育管理研究会协办的全国性高等教育管理专业期刊。 ISSN 1…...

全国计算机二级考试C语言篇4——选择题

运算符与表达式 1.赋值的正确写法 赋值操作是一个很常见的操作&#xff0c;但是赋值操作也有一些需要注意的地方。赋值操作是将一个表达式的值赋给一个变量的过程。在C语言中&#xff0c;赋值操作符是""。结合性从右到左&#xff0c;不控制求值顺序。 下面是几种C语言…...

数据结构————哈希表

哈希表&#xff08;Hash table&#xff09;&#xff0c;也被称为散列表&#xff0c;是一种根据关键值&#xff08;Key value&#xff09;而直接进行访问的数据结构。它通过把关键值映射到表中的一个位置来访问记录&#xff0c;从而加快查找的速度。这个映射函数被称为散列函数或…...

element select + tree

element select tree的使用 <template slot"action1" slot-scope"text, record, index"><el-select v-model"record.tagValue" multiple placeholder"请选择":filter-method"(e) > filterTree(e, index)" filt…...

LeetCode之矩阵

36. 有效的数独 class Solution {// 方法 isValidSudoku 接收一个字符二维数组 board&#xff0c;表示数独棋盘&#xff0c;返回是否有效public boolean isValidSudoku(char[][] board) {// 创建三个二维数组来记录每一行、列和子框中数字的出现次数int[][] rows new int[9][…...

Windows文件系统介绍与基本概念解析

1. 引言 1.1 什么是文件系统 文件系统是一种管理和组织计算机存储设备上文件和文件夹的方法。它提供了文件的创建、读取、写入、删除等操作,并负责文件在存储设备上的存储和访问。 在操作系统中,文件系统是一个重要的组成部分,它使得用户可以方便地使用计算机存储设备来存…...

使用 Apache POI 实现 Java Word 模板占位符替换功能

使用 Apache POI 实现 Java Word 模板占位符替换功能 在日常开发中&#xff0c;我们经常会遇到生成 Word 文档的需求&#xff0c;特别是在需要从模板导出 Word 文件时&#xff0c;比如生成合同、报告等。通过使用模板&#xff0c;开发者可以减少重复的工作&#xff0c;将预定义…...

第三届人工智能与智能信息处理国际学术会议(AIIIP 2024)

目录 大会介绍 基本信息 合作单位 主讲嘉宾 会议组委 征文主题 ​ 参会方式 会议日程 中国-天津 | 2024年10月25-27日 | 会议官网&#xff1a;www.iiip.net 大会介绍 第三届人工智能与智能信息处理国际学术会议&#xff08;AIIIP 2024&#xff09;将于202…...

【动手学深度学习】04 数据操作 + 数据预处理(个人向笔记)

数据操作 N维数组是机器学习和神经网络的主要数据结构其中 2-d 矩阵中每一行表示每一行表示一个样本 当维度来到三维的时候则可以表示成一张图片&#xff0c;再加一维就可以变成多张图片&#xff0c;再加一维则可以变成一个视频 访问元素 冒号表示从冒号左边的元素到冒号右…...

本地搭建 Whisper 语音识别模型

Whisper 是由 OpenAI 开发的一款强大的语音识别模型&#xff0c;具有出色的多语言处理能力。搭建和使用 Whisper 模型可以帮助您将音频内容转换为文本&#xff0c;这在语音转写、语音助手、字幕生成等应用中都具有广泛的用途。本指南将对如何在本地环境中搭建 Whisper 语音识别…...

数据集成-缝合一套数据仓库Infra的臆想

一、数据集成当前困境 目前数据集成基础设施建设仅一个单一数据库&#xff0c;无法很好支持上层应用的建设步骤&#xff0c;继续采用当前设施跟随产品的策略&#xff0c;数据产品开发受限巨大&#xff0c;从目前实施的几个产品看&#xff0c;存在以下主要问题&#xff1a; 功能…...

运营有哪几种?

运营又有很多类&#xff0c;分为&#xff1a;内容运营、用户运营、活动运营、产品运营、新媒体运营、社群运营、电商运营、短视频运营 1.内容运营&#xff1a; 做内容提升各类数据&#xff0c;比如内容的数量/浏览数量/互动数传播数等。 适合人群&#xff1a;适合喜欢看文章热…...

Android视频编辑:利用FFmpeg实现高级功能

在移动设备上进行视频编辑的需求日益增长&#xff0c;用户期望能够在智能手机或平板电脑上轻松地编辑视频&#xff0c;以满足社交媒体分享或个人存档的需求。Android平台因其广泛的用户基础和开放的生态系统&#xff0c;成为视频编辑应用的理想选择。FFmpeg&#xff0c;作为一个…...

图片无损缩放PhotoZoom Pro 9.0.2绿色版 +免费赠送PhotoZoom激活优惠代码

PhotoZoom Pro 9.0.2 是一款专业的图片无损缩放软件&#xff0c;该软件采用了 benvista s-spline 独特技术&#xff0c;增强了对图像格式的支持&#xff0c;多处理器支持&#xff0c;GPU 加速&#xff0c;win10和 Photoshop CC 支持。带来一流的数字图形扩展与缩减技术。该软件…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

网络编程(UDP编程)

思维导图 UDP基础编程&#xff08;单播&#xff09; 1.流程图 服务器&#xff1a;短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...