ThreadLocal原理及使用

一、引言

在Java多线程编程中,ThreadLocal是一个非常有用的工具,它提供了一种将对象与线程关联起来的机制,使得每个线程都可以拥有自己独立的对象副本,从而避免了线程安全问题。然而,使用不当会导致内存泄漏问题。

二、ThreadLocal介绍

ThreadLocal是一个线程本地变量(与其说是线程本地变量,不如说是线程局部变量),它为每个线程提供了一个独立的副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程的副本。ThreadLocal通常用于解决线程安全问题,例如在多线程环境下共享对象时,可以使用ThreadLocal来保存每个线程独立的对象副本,从而避免了同步操作。下面笔者提供一个代码案例来说明它的用法。

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 日期工具类* @author hulei*/
public class DateUtil {private static final SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static Date parse(String dateString) {Date date = null;try {date = simpleDateFormat.parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

上面是一个日期工具类,内部定义了一个日期格式转换方法parse(),还有一个日期格式转换器SimpleDateFormat类。

多线程测试代码如下

package com.execute.batch.executebatch;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** @author hulei* @date  2024/5/23 15:44*/public class ThreadLocalTest {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for (int i = 0; i < 10; i++) {executorService.execute(()->{System.out.println(DateUtil.parse("2024-05-23 16:34:30"));});}executorService.shutdown();}
}

测试结果报错
在这里插入图片描述

把工具类的

    private static final SimpleDateFormat simpleDateFormat =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

替换成如下写法,用ThreadLocal包起来

    private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

工具类变成如下

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*** 日期工具类* @author hulei*/
public class DateUtil {private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String dateString) {Date date = null;try {date = dateFormatThreadLocal.get().parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

测试发现不报错了

package com.execute.batch.executebatch;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/*** 日期工具类* @author hulei*/
public class DateUtil {private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String dateString) {Date date = null;try {date = dateFormatThreadLocal.get().parse(dateString);} catch (ParseException e) {e.printStackTrace();}return date;}
}

刚才第一次测试报错,是因为SimpleDateFormat不是线程安全的类,SimpleDateFormat 不是线程安全的主要原因在于以下几个方面:

  • 内部状态共享:SimpleDateFormat 内部维护了一些状态,如日期字段的解析和格式化信息。这些状态在解析或格式化日期时可能会被修改。当多个线程同时访问一个实例时,如果没有适当的同步控制,这些状态的修改可能会发生冲突,导致不一致的结果。

  • 可变性:SimpleDateFormat 实例是可以修改的。比如,可以通过调用 applyPattern() 方法来改变其格式模式,这会影响实例的状态。如果多个线程同时修改同一个实例,可能会出现竞态条件。

  • 缓存行为:SimpleDateFormat 在解析日期时,可能会缓存一些日期字段的解析结果,这些缓存是基于实例的。如果多个线程同时访问,可能会导致缓存的数据不准确或丢失。

  • 线程本地副本:在某些情况下,SimpleDateFormat 实例可能需要使用线程本地副本来提高性能,但Java的标准实现并未内置这样的机制,所以开发者需要手动处理线程安全问题。

为了避免这些问题,有几种常见的解决方案:

  • 线程局部实例:为每个线程创建单独的 SimpleDateFormat 实例,避免共享。

  • 同步访问:如果必须共享实例,可以在访问时使用 synchronized 关键字或 java.util.concurrent.locks.Lock 进行同步。

  • 使用不可变的 DateTimeFormatter:Java 8及更高版本提供了 java.time.format.DateTimeFormatter 类,它是线程安全的,可以替代 SimpleDateFormat。

在多线程环境中,使用 ThreadLocal 是一个好的选择,因为它可以确保每个线程拥有自己SimpleDateFormat 实例,从而消除线程安全问题。

三、内存泄露问题

虽然ThreadLocal提供了一种便捷的线程封闭机制,但是如果使用不当会导致内存泄漏问题。ThreadLocal的内存泄漏问题主要表现在以下两个方面:

  1. 线程结束后没有手动清理
    当一个线程结束后,它所持有的ThreadLocal变量并不会立即释放,如果没有手动调用remove()方法清理ThreadLocal变量,那么这些变量会一直保留在内存中,直到线程池被销毁或者应用程序退出。

  2. ThreadLocal变量被弱引用持有
    ThreadLocal内部通过一个ThreadLocalMap来存储线程独立的变量副本,而ThreadLocalMap中的Entry是由ThreadLocal的弱引用持有的。如果一个ThreadLocal没有被外部强引用持有,那么在垃圾回收时,ThreadLocal对象会被回收,但是对应的Entry并不会被自动清理,这样就会导致内存泄漏问题。

四、避免内存泄漏

为了避免ThreadLocal的内存泄漏问题,我们可以采取以下几种解决方案:

及时清理ThreadLocal变量

在使用完ThreadLocal变量后,应该及时调用remove()方法清理ThreadLocal变量,以便释放资源。

ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("value");
// 使用完毕后清理ThreadLocal变量
threadLocal.remove();

日期转换工具类代码可以加入以下语句清理ThreadLocal变量
在这里插入图片描述

使用ThreadLocal的弱引用

为了避免ThreadLocal对象被强引用持有导致的内存泄漏问题,可以将ThreadLocal声明为静态内部类,以使得ThreadLocal对象的生命周期比较长,从而避免了被短生命周期的线程持有。意思是生命为静态内部变量,大致如下:

public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new ThreadLocal<>();// 省略其他代码
}

使用InheritableThreadLocal

InheritableThreadLocal是ThreadLocal的一个子类,它可以让子线程从父线程中继承ThreadLocal变量,但是使用InheritableThreadLocal也会增加内存泄漏的风险,因此需要谨慎使用。

public class MyThreadLocal {private static final ThreadLocal<Object> threadLocal = new InheritableThreadLocal<>();// 省略其他代码
}

注意:实际java8以后的版本,ThreadLocal的实现包含了一个弱引用机制,当线程结束时,即使未手动调用remove(),与线程相关的ThreadLocalMap.Entry也会有机会被垃圾回收器回收,从而减少了内存泄漏的风险。但这种机制并不能完全排除内存泄漏,特别是在长期运行的线程或线程池中,如果ThreadLocal的引用没有被及时清理,仍然可能导致大量无用对象占据内存空间。所以仍然建议手动释放掉ThreadLocal变量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/14090.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

go 微服务框架kratos错误处理的使用方法及原理探究

通过go语言原生http中响应错误的实现方法&#xff0c;逐步了解和使用微服务框架 kratos 的错误处理方式&#xff0c;以及探究其实现原理。 一、go原生http响应错误信息的处理方法 处理方法&#xff1a; ①定义返回错误信息的结构体 ErrorResponse // 定义http返回错误信息的…

无人机飞手前途分析

无人机飞手的前途充满了各种可能性和挑战&#xff0c;这主要得益于无人机技术的快速发展和广泛应用。以下是对无人机飞手前途的一些分析&#xff1a; 1. 技术发展与需求增长&#xff1a;随着无人机技术的不断进步&#xff0c;其应用场景也在持续扩大。从地理测绘、巡检、农林植…

利用阿里OSS服务给文件设置过期删除--简单版

在云存储广泛应用的今天&#xff0c;阿里云的Object Storage Service&#xff08;OSS&#xff09;以其高度可扩展性、安全性和成本效益&#xff0c;成为了众多企业和开发者存储海量数据的首选方案。随着数据量的不断膨胀&#xff0c;高效的数据管理和成本控制变得尤为重要。其中…

IT学习笔记--Kafka

Kafka概述: 定义: Kafka是一个分布式的基于发布/订阅模式的消息队列&#xff0c;主要应用于大数据实时处理领域。 消息队列消息队列的两种模式: 点对点模式: 消息生产者生产消息发送到Queue中&#xff0c;然后消息消费者从Queue中取出并且消费消息。 消息被消费以后&#…

Linux中解决普通用户使用不了sudo问题

目录 sudo的使用场景sudo使用不了的原因解决方法 sudo的使用场景 之前我们介绍了文件的权限问题 如果一个普通用户想去执行一个它命令之外的权限&#xff0c;只能使用sudo 比如普通用户使用yum去安装软件&#xff0c;需要sudo yum xxxx sudo使用不了的原因 这里我们用普通用户…

小恐龙跳一跳源码

小恐龙跳一跳源码是前两年就火爆过一次的小游戏源码&#xff0c;不知怎么了今年有火爆了&#xff0c;所以今天就吧这个源码分享出来了&#xff01;有喜欢的直接下载就行&#xff0c;可以本地单机直接点击index.html进行运行&#xff0c;又或者放在虚拟机或者服务器上与朋友进行…

python 获取视频的时长

以下是几种获取视频时长的实现方法&#xff1a; 方法一&#xff1a;使用moviepy库 from moviepy.editor import VideoFileClipdef get_video_duration(file_path):video VideoFileClip(file_path)duration video.durationvideo.close()return duration 方法二&#xff1a;…

SAP-FICO-凭证编号控制

成本凭证编号KANK 如果自己的公司下没有&#xff0c;直接复制系统原有的就可以。使用系统默认即可。 如果不维护 会报错“CO-凭证编号分配对于成本控制范围****中的商业事务COIN无效” 财务凭证编号FBN1 可以用OBH2批量复制编号范围。 物料账期MMPV 财务账期OB52

python使用base加密解密

原理 base编码是一种加密解密措施&#xff0c;目前常用的有base16、base32和base64。其大致原理比较简单。 以base64为例&#xff0c;base64加密后共有64中字符。其加密过程是编码后将每3个字节作为一组&#xff0c;这样每组就有3*824位。将每6位作为一个单位进行编码&#xf…

1个逗号,提升Python代码质量

有些时候&#xff0c;我们会在Python代码中看到列表或其他科迭代对象的结尾会存在一个逗号&#xff1a; 而且编辑器和解释器都容许这种逗号的存在&#xff0c;它就叫作拖尾逗号。 通常是为了在频繁地增减数组元素的时候同时保证语法的正确&#xff0c;且拖尾逗号不占用数组的长…

MySQL 主备环境搭建 docker

MySQL 主备环境搭建 docker 拉取docker镜像 sudo docker pull mysql:8.0 启动容器 docker run -p 3339:3306 --name mysql-master -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0docker run -p 3340:3306 --name mysql-slave -e MYSQL_ROOT_PASSWORD123456 -d mysql:8.0配置 M…

第四十二天 | 背包问题理论

二维&#xff1a; 1.dp[i][j] 表示从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和最大是多少。 2.递归公式&#xff1a; dp[i][j] max(dp[i - 1][j], dp[i - 1][j - weight[i]] value[i]); 3.初始化&#xff1a; 首先从dp[i][j]的定义出发…

基于xilinx fpga RFSOC系列的Ultrascale+ RF Data Converter ip详解说明

目录 1 概述2 IP功能2.1 ADC性能2.2 DAC性能3 IP端口4 代码框架4.1 ADC功能框图4.2 DAC功能框图5 收发数据时序5.1 ADC数据格式5.2 DAC数据格式6 时钟配置6.1 ADC/DAC参考时钟7 数据格式配置模式7.1 ADC的配置模式7.1.1 Real -> real;7.1.2 Real ->IQ;7.1.3 IQ -> IQ;…

【设计模式】JAVA Design Patterns——Bridge(桥接模式)

&#x1f50d;目的 将抽象与其实现分离&#xff0c;以便二者可以独立变化。 &#x1f50d;解释 真实世界例子 考虑一下你拥有一种具有不同附魔的武器&#xff0c;并且应该允许将具有不同附魔的不同武器混合使用。 你会怎么做&#xff1f; 为每个附魔创建每种武器的多个副本&…

当代人工智能三教父——深度学习三巨头

文章目录 引言 人物介绍 突出贡献 专业名词解释 引言 今天下午闲来无事翻阅了一下csdn首页的头条文章——《27 岁天才创始人 Joel Hellermark 分享了自己和“AI 教父” Geoffery Hinton 的最新采访》 感觉挺有意思&#xff0c;就从头到尾的看了一遍&#xff0c;里面有很多…

pyqt5与yolov5进行视频检测(一)——登录操作

项目效果展示 一、登录界面 二、主界面 目前在更新中。。。 一、设计 二、登录代码 注意&#xff1a;下面会导入主界面的包&#xff0c;图片资源自己设计一下&#xff0c;密码保存时没设计加密&#xff0c;需要自行设计 main_window主界面下文会设计from main_window impor…

无线通信的穿墙能力主要取决于哪些指标

无线通信的穿墙能力是指无线信号在穿越建筑物墙壁时&#xff0c;其信号衰减程度以及能否维持足够强度以进行稳定通信的能力。穿墙能力的好坏直接影响到无线通信在室内环境中的覆盖范围和使用体验。 一、无线信号的频率 无线信号的频率是影响穿墙能力的重要因素之一。一般来说…

工行音视频服务平台建设与应用经验

近些年来&#xff0c;伴随着技术能力的积累突破&#xff0c;音视频服务开始蓬勃生长走进千家万户&#xff0c;使用远程视频通话、观看各类视频直播逐渐成为人们的日常&#xff0c;而金融服务作为社会生活的重要组成部分&#xff0c;自然需要积极拥抱应用新技术。 如今&#xff…

怎么知道Python包的依赖项

要查看Python包的依赖项,有几种方法可以做到这一点: 使用pip: pip是Python的包管理器,它允许你安装和管理Python库。要查看一个包的依赖关系,你可以使用pip show命令加上包名,但请注意,直接用pip show并不直接列出依赖项,它提供包的详细信息,包括它的安装路径。为了查看…

Kubernetes Deployment 之扩缩容与滚动更新

Kubernetes Deployment 之扩缩容与滚动更新 Deployment 扩缩容 扩缩容非常简单&#xff0c;我们可以直接调整 replica 副本数目&#xff0c;然后 kubectl apply指定进行动态更新。下面将nginx-deployment动态改为 1 个 Pod 和 3 个 Pod 的操作 apiVersion: apps/v1 kind: De…