ThreadLocal原理及使用

news2024/11/18 12:31:03

一、引言

在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.coloradmin.cn/o/1683825.html

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

相关文章

spring-boot整合Micrometer+Prometheus

环境&#xff1a; micrometer 1.8.2 prometheus 0.14.1 spring-boot-actuator 2.6.6 使用案例 <!-- Springboot启动actuator&#xff0c;默认会引入依赖&#xff1a;micrometer-core --> <dependency><groupId>org.springframework.boot</groupId>&l…

ctfhub中的SSRF相关例题(中)

目录 上传文件 gopher协议的工作原理&#xff1a; gopher协议的使用方法&#xff1a; 相关例题: FastCGI协议 FastCGI协议知识点 相关例题&#xff1a; Redis协议 知识点&#xff1a; 相关例题 第一种方法 第二种方法 上传文件 gopher协议的工作原理&#xff1a; …

《ESP8266通信指南》番外-(附完整代码)ESP8266获取DHT11接入(基于Lua)

前言 此篇为番外篇,是 ESP8266 入门的其他功能教程,包括但不限于 DHT11 驱动TCP 通信Thingsboard 平台的接入阿里云物联网云平台接入华为云平台接入 1. 小节目标 使用 Lua 驱动 DHT11 传感器,获取温湿度的值 2. 进入主题 NodeMCU 基于 LUA 相关资料 官方文档&#xff1a;…

商品指数创年内新高,粘性通胀成为美联储噩梦

文章概述 虽然美国4月CPI增幅放缓让美联储今年降息的可能性大增&#xff0c;但与此同时&#xff0c;大宗商品价格却达到了一年来的最高水平&#xff0c;粘性通胀可能成为美联储的噩梦。数据显示&#xff0c;跟踪24种能源、金属和农业合约彭博大宗商品现货指数今年以来已经上涨…

Mysql超详细安装配置教程(保姆级图文)

MySQL是一种流行的开源关系型数据库管理系统&#xff0c;它广泛用于网站和服务的数据存储和管理。MySQL以其高性能、可靠性和易用性而闻名&#xff0c;是许多Web应用程序的首选数据库解决方案之一。 一、下载安装包 &#xff08;1&#xff09;从网盘下载安装文件 点击此处直…

RK3588 Android13 TvSetting 中增加字体样式切换功能

前言 电视产品,客户需求又升级了,有了切换字体大小还不行,还得增加动态切换字体样式功能, 同样需要在设备偏好设置子菜单里的显示和声音二级菜单里增加字体样式菜单功能,开整。 效果图 framework 部分修改文件清单 frameworks/base/data/fonts/fonts.mk frameworks/bas…

附代码:策略常用-正余弦优化算法

正余弦优化算法作为群智能优化算法的一种, 正弦余弦算法 (sine cosine algorithm, SCA) 是 2016 年由 Mirjalili 提出的一种新型仿自然优化算法, 通过创建多个随机候选解, 利用正余弦函数的数学性质来平衡算法在搜系过程中的全局探索和局部开发能力。该算法具有结构简单、参数少…

MobaXterm:Network error: Connection refused

问题描述 使用MobaXterm连接服务器或者虚拟机里面的操作系统显示“Network error: Connection refused” 因为服务器或者虚拟机里面的操作系统没安装 ssh 解决方法 安装ssh sudo apt-get update sudo apt-get upgrade sudo apt-get install ssh重启 ssh service ssh resta…

Docker 镜像是什么?

Docker 镜像是什么&#xff1f; Docker 镜像&#xff08;Docker Image&#xff09;是用于创建 Docker 容器的只读模板。它包含了运行应用程序所需的所有内容&#xff0c;包括代码、运行时环境、库、环境变量以及配置文件。Docker 镜像是构建和分发应用程序的基础。 在深入阅读…

[数据集][目标检测]弹簧上料检测数据集VOC+YOLO格式142张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;142 标注数量(xml文件个数)&#xff1a;142 标注数量(txt文件个数)&#xff1a;142 标注类别…

如何保护好源代码

在信息技术飞速发展的今天&#xff0c;源代码作为软件开发的核心要素&#xff0c;其安全性与保密性至关重要。一旦源代码泄露或被恶意篡改&#xff0c;将可能导致企业面临重大损失&#xff0c;甚至威胁到整个行业的安全。因此&#xff0c;如何保护源代码已成为软件企业和个人开…

15.1使用curl命令,命令行模拟登陆discuz

使用curl命令,命令行模拟登陆discuz web保存session&#xff0c;鼠标点一点&#xff0c;发起http请求&#xff0c;html 注意不能使用登录带验证码的网站测试 1.curl命令模拟访问discuz论坛 在192.168.111.16服务器的web站点新建一个目录&#xff0c;获取cookie信息与html文件…

IP学习——ospf1

OSPF:开放式最短路径优先协议 无类别IGP协议&#xff1a;链路状态型。基于 LSA收敛&#xff0c;故更新量较大&#xff0c;为在中大型网络正常工作&#xff0c;需要进行结构化的部署---区域划分、ip地址规划 支持等开销负载均衡 组播更新 ---224.0.0.5 224.0.0.6 …

一剪梅-答赠云安客刘自果

当众网友看了笔者“边吸氧边动鼠标”的短视频之后&#xff0c;纷纷发来微信问候。其中我的远房亲戚&#xff0c;那个正在潜心写作数十万字的长篇纪实文学《川江向东流》的66岁贤弟刘自果&#xff08;号云安客&#xff0c;亦称自果居士&#xff09;&#xff0c;发来微信鼓励我&a…

mysql 多表关联查询性能优化-同一sql不同的执行计划

一、问题背景 相同的sql&#xff0c;不同的日期&#xff0c;执行的时间差异很大&#xff0c;执行计划不一样。执行快时&#xff0c;30ms左右。执行慢时&#xff0c;15s左右。 二、分析结论 1、经过分析&#xff0c;发现不同日期下&#xff0c;sql的执行计划不同&#xff0c;驱…

《Effective Objective-C 2.0》读书笔记——熟悉Objective-C

目录 第一章&#xff1a;熟悉Objective-C第1条&#xff1a;了解Objective-C语言的起源第2条&#xff1a;在类的头文件中尽量少引入其他头文件第3条&#xff1a;多用字面量语法&#xff0c;少用与之等价的方法第4条&#xff1a;多用类型常量&#xff0c;少用#define预处理指令第…

网络世界的盗梦空间:用Crawley框架破解数据维度

嗨&#xff0c;我是阿佑&#xff0c;你是否设想过自己能够像电影中的盗梦者一样&#xff0c;潜入网站深层&#xff0c;巧妙抓取那些隐藏在数字幻境中的数据宝藏&#xff1f;今天阿佑将带你体验前所未有的数据探险&#xff0c;让你在Python的海洋中乘风破浪&#xff0c;成为数据…

【漏洞复现】用友U8 CRM uploadfile 文件上传致RCE漏洞

0x01 产品简介 用友U8 Cloud是用友推出的新一代云ERP&#xff0c;主要聚焦成长型、创新型企业&#xff0c;提供企业级云ERP整体解决方案。 0x02 漏洞概述 用友 U8 CRM客户关系管理系统 uploadfle.php 文件存在任意文件上传漏洞&#xff0c;未经身份验证的攻击者通过漏洞上传…

open drain 与 push pull

Open drain: open drain 输出&#xff1a;输出端相当于三极管的集电极&#xff0c;要得到高电平需要上拉电阻才行。 栅极输入为0时&#xff0c;NMOS 的漏极和源极导通&#xff0c;输出为0。即Uce 0 V。 栅极输入为1时&#xff0c;NMOS不导通&#xff0c;漏极高祖&#xff0…

防静电液的这些用处你知道多少

防静电液又叫抗静电剂&#xff0c;是工业上常用来消除静电的化学用品&#xff0c;一般是液体状态&#xff0c;它的用途很广泛。 防静电液适用于对静电有控制要求的电器、仪器桌面、台面、塑料制品、包装品、存储盒、托盘、毛毯、织物等任何物品表面。 应用举例如消除各种塑胶材…