ThreadLocal的应用场景
ThreadLocal介绍
ThreadLocal为每个线程都提供了变量的副本,使得每个线程访问各自独立的对象,这样就隔离了多个线程对数据的共享,使得线程安全。ThreadLocal有如下方法:
| 方法声明 | 描述 |
| public void set(T value) | 设置当前线程绑定的局部变量 |
| public T get() | 获取当前线程绑定的局部变量 |
| public void remove() | 移除当前线程绑定的局部变量 |
| protected Object initialValue() | 初始化值 |
ThreadLocal应用场景
场景一:每个线程需要一个独享的对象(典型的需要使用的类就是 SimpleDateFormat,因为它是线程不安全的)
package com.gingko.threadlocal;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;//线程不安全
public class DateUtils1 {private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static String formatDate(long seconds) {Date date = new Date(seconds*1000);String format = dateFormat.format(date);return format;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for(int i=0;i<100;i++) {int finalI = i;/*** 10个线程共享1个SimpleDateFormat,会发生线程安全问题,运行结果出现相同的时间*/executorService.submit(()-> {try {String formatDate = formatDate(finalI);System.out.println(formatDate);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}executorService.shutdown();}
}
运行结果:

分析:线程池创建了10个线程处理100个任务,10个线程共享1个SimpleDateFormat,发生了线程安全问题,运行结果出现相同的时间。
改进版本:加锁机制
package com.gingko.threadlocal;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//使用锁机制,可以解决线程安全问题,多线程时等待,执行效率较低
public class DateUtils2 {private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//使用锁机制,多线程时等待,执行效率较低public static synchronized String formatDate(long seconds) {Date date = new Date(seconds*1000);String format = dateFormat.format(date);return format;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for(int i=0;i<100;i++) {int finalI = i;/*** 10个线程共享1个SimpleDateFormat*/executorService.submit(()-> {try {String formatDate = formatDate(finalI);System.out.println(formatDate);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}executorService.shutdown();}
}
运行结果:

分析:线程池创建了10个线程处理100个任务,10个线程共享1个SimpleDateFormat,在formatDate方法上加了锁,使得多个线程同时执行此方法时需要排队等待获取锁,执行结果没有问题,但是由于要等待获取锁,执行效率低。
改进版本:使用ThreadLocal
package com.gingko.threadlocal;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//使用threadlocal,使得每个线程有各个独立的SimpleDateFormat
public class DateUtils3 {//使用threadlocal,使得每个线程有各个独立的SimpleDateFormatprivate static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(()-> {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");});public static String formatDate(long seconds) {Date date = new Date(seconds*1000);String format = dateFormatThreadLocal.get().format(date);return format;}public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(10);for(int i=0;i<100;i++) {int finalI = i;/*** 10个线程有各自独立的SimpleDateFormat,不会发生线程安全问题*/executorService.submit(()-> {try {String formatDate = formatDate(finalI);System.out.println(formatDate);Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});}executorService.shutdown();}
}
运行结果:
分析:线程池创建了10个线程处理100个任务,10个线程独占各自的SimpleDateFormat,执行结果没有问题,没有锁机制,执行效率高。
场景二:替代参数链传递

上图中通过前台获取到用户信息后,一路向下传递,假设方法A、B、C都需要用户信息,一种方式是A、B、C方法都接收用户信息作为入参(非常繁琐),一种方式是将用户信息放入ThreadLocal中,这样线程链上的所有方法都可以获取到用户信息,不用在方法A、B、C显式的指定User Info的入参,类似的应用场景还有:前台传递的分页参数等。
package com.gingko.threadlocal;import com.gingko.entity.Student;public class TransferParam {//线程中放入student信息,整个线程链路上都可以获取student信息private static ThreadLocal<Student> studentThreadLocal = new ThreadLocal<>();public static void main(String[] args) {TransferParam transferParam = new TransferParam();transferParam.setParam();}public void setParam() {Student student = new Student("1","张三",18,"001");studentThreadLocal.set(student);new ServiceA().getParam();}class ServiceA {public void getParam() {Student student = studentThreadLocal.get();System.out.println("ServiceA:" + student);new ServiceB().getParam();}}class ServiceB {public void getParam() {Student student = studentThreadLocal.get();System.out.println("ServiceB:" + student);//线程链路的最后删除threadlocal信息,防止发生内存泄露studentThreadLocal.remove();}}
}
运行结果:

从结果上看出:在方法setParam设置了ThreadLocal的变量student,在其线程调用的链条上方法:ServiceA.getParam 和ServiceB.getParam 都可以获取到student
注意:在线程链最后的方法上记得调用ThreadLocal的remove方法,不然会出现内存泄漏的风险,这块内容后续的章节会介绍。
相关文章:
ThreadLocal的应用场景
ThreadLocal介绍 ThreadLocal为每个线程都提供了变量的副本,使得每个线程访问各自独立的对象,这样就隔离了多个线程对数据的共享,使得线程安全。ThreadLocal有如下方法: 方法声明 描述public void set(T value)设置当前线程绑定的…...
Python--plt.errorbar学习笔记
plt.errorbar 是 Matplotlib 库中的一个函数,用于绘制带有误差条的图形。下面给出的代码行的详细解释: import numpy as np from scipy.special import kv, erfc from scipy.integrate import dblquad import matplotlib.pyplot as plt import scipy.in…...
文件信息类QFileInfo
常用方法: 构造函数 //参数:文件的绝对路径或相对路径 [explicit] QFileInfo::QFileInfo(const QString &path) 设置文件路径 可构造一个空的QFileInfo的对象,然后设置路径 //参数:文件的绝对路径或相对路径 void QFileI…...
堆排序(C++实现)
参考: 面试官:请写一个堆排序_哔哩哔哩_bilibiliC实现排序算法_c从小到大排序-CSDN博客 堆的基本概念 堆排实际上是利用堆的性质来进行排序。堆可以看做一颗完全二叉树。 堆分为两类: 最大堆(大顶堆):除根…...
Qt中加入UI文件
将 UI 文件整合到 Qt 项目 使用 Qt Designer 创建 UI 文件: 在 Qt Creator 中使用 Qt Designer 创建 UI 文件,设计所需的界面。确保在设计中包含所需的控件(如按钮、文本框等),并为每个控件设置明确的对象名称…...
Redisson使用全解
redisson使用全解——redisson官方文档注释(上篇)_redisson官网中文-CSDN博客 redisson使用全解——redisson官方文档注释(中篇)-CSDN博客 redisson使用全解——redisson官方文档注释(下篇)_redisson官网…...
Go4 和对 Go 的贡献
本篇内容是根据2017年4月份Go4 and Contributing to Go音频录制内容的整理与翻译, Brad Fitzpatrick 加入节目谈论成为开源 Go 的代言人、让社区参与 bug 分类、Go 的潜在未来以及其他有趣的 Go 项目和新闻。 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. Erik St…...
区间动态规划
区间动态规划(Interval DP)是动态规划的一种重要变种,特别适用于解决一类具有区间性质的问题。典型的应用场景是给定一个区间,要求我们在满足某些条件下进行最优划分或合并。本文将从区间DP的基本思想、常见问题模型以及算法实现几…...
什么情况下需要使用电压探头
高压探头是一种专门设计用于测量高压电路或设备的探头,其作用是在电路测试和测量中提供安全、准确的信号捕获,并确保操作人员的安全。这些探头通常用于测量高压电源、变压器、电力系统、医疗设备以及其他需要处理高电压的设备或系统。 而高压差分探头差分…...
数据结构——八大排序(下)
数据结构中的八大排序算法是计算机科学领域经典的排序方法,它们各自具有不同的特点和适用场景。以下是这八大排序算法的详细介绍: 五、选择排序(Selection Sort) 核心思想:每一轮从未排序的元素中选择最小࿰…...
Linux系统:Ubuntu上安装Chrome浏览器
Ubuntu系统版本:23.04 在Ubuntu系统上安装Google Chrome浏览器,可以通过以下步骤进行: 终端输入以下命令,先更新软件源: sudo apt update 或 sudo apt upgrade终端输入以下命令,下载最新的Google Chrome .…...
Redis位图BitMap
一、为什么使用位图? 使用位图能有效实现 用户签到 等行为,用数据库表记录签到,将占用很多存储;但使用 位图BitMap,就能 大大减少存储占用 二、关于位图 本质上是String类型,最小长度8位(一个字…...
YOLOv11改进策略【卷积层】| ParNet 即插即用模块 二次创新C3k2
一、本文介绍 本文记录的是利用ParNet中的基础模块优化YOLOv11的目标检测网络模型。 ParNet block是一个即插即用模块,能够在不增加深度的情况下增加感受野,更好地处理图像中的不同尺度特征,有助于网络对输入数据更全面地理解和学习,从而提升网络的特征提取能力和分类性能…...
学习threejs,网格深度材质MeshDepthMaterial
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️网格深度材质MeshDepthMate…...
算法时间、空间复杂度(二)
目录 大O渐进表示法 一、时间复杂度量级的判断 定义: 例一:执行2*N+1次 例二:执行MN次 例三:执行已知次数 例四:存在最好情况和最坏情况 顺序查找 冒泡排序 二分查找 例五:阶乘递归 编辑 例…...
高级java每日一道面试题-2024年10月11日-数据库篇[Redis篇]-Redis都有哪些使用场景?
如果有遗漏,评论区告诉我进行补充 面试官: Redis都有哪些使用场景? 我回答: Redis 是一个开源的、基于键值对的数据结构存储系统,,它支持多种数据类型,包括字符串、散列、列表、集合和有序集合。它可以用作数据库、缓存和消息中间件。由于…...
0047__【python打包分发工具】setuptools详解
【python打包分发工具】setuptools详解-CSDN博客...
自定义拦截器处理token
目录 1、WebConfig 配置类 2、TSUserContext 把用户信息放到context中 3、自定义拦截器 4、在controller中可以使用 5、参考链接 1、WebConfig 配置类 @Configuration public class WebConfig implements WebMvcConfigurer {@Autowiredprivate AccessControlInterceptor …...
Scrapy | 使用Scrapy进行数据建模和请求
scrapy数据建模与请求 数据建模1.1 为什么建模1.2 如何建模1.3如何使用模板类1.4 开发流程总结 目标: 1.应用在scrapy项目中进行建模 2.应用构造Request对象,并发送请求 3.应用利用meta参数在不同的解析函数中传递数据 数据建模 | 通常在做项目的过程中…...
学习笔记——交换——STP(生成树)基本概念
三、基本概念 1、桥ID/网桥ID (Bridege ID,BID) 每一台运行STP的交换机都拥有一个唯一的桥ID(BID),BID(Bridge ID/桥ID)。在STP里我们使用不同的桥ID标识不同的交换机。 (2)BID(桥ID)组成 BID(桥ID)组成(8个字节):由16位(2字节)的桥优先级…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...
基于Flask实现的医疗保险欺诈识别监测模型
基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施,由雇主和个人按一定比例缴纳保险费,建立社会医疗保险基金,支付雇员医疗费用的一种医疗保险制度, 它是促进社会文明和进步的…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join
纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...
LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
(一)单例模式
一、前言 单例模式属于六大创建型模式,即在软件设计过程中,主要关注创建对象的结果,并不关心创建对象的过程及细节。创建型设计模式将类对象的实例化过程进行抽象化接口设计,从而隐藏了类对象的实例是如何被创建的,封装了软件系统使用的具体对象类型。 六大创建型模式包括…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
