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.rhkb.cn/news/329669.html

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

相关文章

【VTKExamples::Texture】第六期 TextureThreshold

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 公众号:VTK忠粉 前言 本文分享VTK样例TextureThreshold,并解析接口vtkTexture,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~Y…

python使用base加密解密

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

MySQL主从复制+读写分离(ShardingJDBC)

MySQL主从复制读写分离 MySQL主从复制介绍二进制日志&#xff1a; MySQL的主从复制原理如下搭建主从复制准备工作主库配置从库配置 测试 读写分离案例ShardingJDBC介绍数据库环境初始工程导入读写分离配置测试1). 保存数据2). 修改数据3). 查询数据4). 删除数据 MySQL主从复制 …

vue3结合element-plus之如何优雅的使用表格

背景 表格组件的使用在后台管理系统中是非常常见的,但是如果每次使用表格我们都去一次一次地从 element-plus 官网去 复制、粘贴和修改成自己想要的表格。 这样一来也说得过去,但是如果我们静下来细想不难发现,表格的使用都是大同小异的,每次都去复制粘贴,对于有很多表格…

【日记】跟奇安信斗智斗勇,败下阵来(416 字)

正文 今天一个客户都没有&#xff0c;让我快怀疑我们银行是不是要倒闭了…… 因为内外网 u 盘不知所踪&#xff0c;所以重新制了一个。深刻体会到了奇安信有多烂。有两个 u 盘&#xff0c;奇安信似乎把主控写坏了&#xff0c;插上电脑有反应&#xff0c;但是看不见盘符&#xf…

Linux中vim的基本使用

目录 vim中的三种模式以及基本操作命令模式(默认模式)插入模式底行模式 命令模式下的命令底行模式下的命令 vim是Linux和Unix环境下最基本的文本编辑器&#xff0c;类似于windows上的记事本 vim和Visual studio相比&#xff0c;vim并不集成&#xff0c;vim只能用来写代码 VS把写…

CopyOnWriteArrayList原理分析

1.简介 JDK1.5之前&#xff0c;由于那个版本尚未退出专门运行在并发环境下的集合&#xff0c;对于List类型&#xff0c;我明只能选择Vector或者Stack这种老古董&#xff0c;但是它们效率太低(全部的增删改方法均被synchronized修饰着&#xff0c;那个时候的synchronized由于还…

【有手就行】使用你自己的声音做语音合成,CPU都能跑,亲测有效

此文介绍在百度飞桨上一个公开的案例&#xff0c;亲测有效。 厌倦了前篇一律的TTS音色了吗&#xff1f;打开短视频听来听去就是那几个声音&#xff0c;快来试试使用你自己的声音来做语音合成吧&#xff01;本教程非常简单&#xff0c;只需要你能够上传自己的音频数据就可以(建议…

Helm安装kafka3.7.0无持久化(KRaft 模式集群)

文章目录 2.1 Chart包方式安装kafka集群 5.开始安装2.2 命令行方式安装kafka集群 搭建 Kafka-UI三、kafka集群测试3.1 方式一3.2 方式二 四、kafka集群扩容4.1 方式一4.2 方式二 五、kafka集群删除 参考文档 [Helm实践---安装kafka集群 - 知乎 (zhihu.com)](https://zhuanlan.…

离散数学--图论

目录 1.简单概念 2.握手定理 3.点割集 4.边割集 5.点连通度和边连通度 6.Dijstra算法&&最短路径 7.有向图的连通性 8.图的矩阵表示 9.欧拉图问题 10.哈密尔顿图 1.简单概念 &#xff08;1&#xff09;这个里面的完全图比较重要&#xff0c;完全图是例如k3,k5这…

vue项目报错:internal/modules/cjs/loader.js:892 throw err;

前言&#xff1a; vue项目中无法正常使用git&#xff0c;并报错情况。 报错信息&#xff1a; internal/modules/cjs/loader.js:892throw err;^ Error: Cannot find module D:\project\sd_wh_yth_front\node_modules\yorkie\src\runner.js 报错处理&#xff1a; npm install y…

局部直方图均衡化去雾算法

目录 1. 引言 2. 算法流程 3. 代码 4. 去雾效果 1. 引言 局部直方图算法是一种基于块的图像去雾方法&#xff0c;它将图像分割为若干个块&#xff0c;并在每个块内计算块的局部直方图。通过对各个块的直方图进行分析和处理&#xff0c;该算法能够更好地适应图像中不同区域的…

九宫格转圈圈抽奖活动,有加速,减速效果

在线访问demo和代码在底部 代码&#xff0c;复制就可以跑 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><tit…

Git远程控制

文章目录 1. 创建仓库1.1 Readme1.2 Issue1.3 Pull request 2. 远程仓库克隆3. 推送远程仓库4. 拉取远程仓库5. 配置Git.gitignore配置别名 使用GitHub可以&#xff0c;采用Gitee也行 1. 创建仓库 1.1 Readme Readme文件相当于这个仓库的说明书&#xff0c;gitee会初始化2两份…

解除网页禁止选择

控制台输入以下命令 复制&#xff1a;javascript:void(document.body.οncοpy) 可选&#xff1a;javascript:void(document.body.onselectstart) 拖拉&#xff1a;javascript:void(document.body.οnmοuseup)

好的架构是进化来的,不是设计来的

很多年前&#xff0c;读了子柳老师的《淘宝技术这十年》。这本书成为了我的架构启蒙书&#xff0c;书中的一句话像种子一样深埋在我的脑海里&#xff1a;“好的架构是进化来的&#xff0c;不是设计来的”。 2015 年&#xff0c;我加入神州专车订单研发团队&#xff0c;亲历了专…

Android ART 虚拟机简析

源码基于&#xff1a;Android U 1. prop 名称选项名称heap 变量名称功能 dalvik.vm.heapstartsize MemoryInitialSize initial_heap_size_ 虚拟机在启动时&#xff0c;向系统申请的起始内存 dalvik.vm.heapgrowthlimit HeapGrowthLimit growth_limit_ 应用可使用的 max…

先进电气技术 —— 控制理论中的“观测器”概述

一、背景 观测器在现代控制理论中的地位十分重要&#xff0c;它是实现系统状态估计的关键工具。观测器的发展历程可以从以下几个方面概述&#xff1a; 1. 起源与发展背景&#xff1a; 观测器的概念源于对系统状态信息的需求&#xff0c;特别是在只能获取部分或间接输出信息…

如何在.NET中集成SignalR

SignalR 简介 SignalR是一个开放源代码库&#xff0c;可用于简化向应用添加实时Web功能&#xff0c;实时Web功能使服务器端代码能够将内容推送到客户端。 SignalR开源库&#xff1a;https://github.com/SignalR/SignalR SignalR 应用场景 需要高频次从服务器获取信息的应用&am…

java-spring 14 项目启动过程

Spring的启动流程可以归纳为三个步骤&#xff1a; 1、初始化Spring容器&#xff0c;注册内置的BeanPostProcessor的BeanDefinition到容器中 2、将配置类的BeanDefinition注册到容器中 3、调用refresh()方法刷新容器 // 初始化容器 public AnnotationConfigApplicationContex…