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

深入理解Java中的ThreadLocal

第1章:引言

大家好,我是小黑。今天咱们来聊聊ThreadLocal。首先,让咱们先搞清楚,ThreadLocal是个什么玩意儿。简单说,ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着,每个线程都可以独立地改变自己的副本,而不会影响其他线程。这就像是每个人都有自己的笔记本,记录自己的心得体会,互不干扰。

ThreadLocal的作用

ThreadLocal的这个特性,在多线程环境下特别有用。咱们知道,多线程编程中最头疼的就是线程安全问题。如果多个线程共享同一个变量,很容易出现线程间的数据混乱。而ThreadLocal提供了一种优雅的解决方式,让每个线程都有自己独立的变量副本,互不干扰,自然就规避了这些问题。

应用场景

在实际开发中,ThreadLocal的用途非常广泛。比如,在Web开发中,咱们可以用它来存储每个用户的会话信息。又比如,在数据库连接管理中,ThreadLocal可以帮助咱们管理每个线程的数据库连接,确保不同线程间的数据库操作互不干扰。

第2章:线程与内存管理

线程基础

说到ThreadLocal,咱们得先回顾一下线程的基本概念。在Java中,线程是执行任务的基本单位。每个Java程序至少有一个线程:主线程。而在复杂的应用中,通常会有多个线程同时运行,分摊任务,提高效率。

Java内存模型

接下来,咱们聊聊Java的内存模型。在Java中,内存大致分为两部分:堆(Heap)和栈(Stack)。堆是所有线程共享的内存区域,用于存储对象实例;栈则是线程私有的,存储局部变量和方法调用。这就是为什么线程间可以通过共享对象来通信,但同时也容易引发线程安全问题。

线程安全性

所谓线程安全,指的是多线程环境下,不同线程操作共享数据时,能保证数据的准确性和一致性。在Java中,保证线程安全的常见做法有:使用synchronized关键字,利用并发包中的工具类,以及——咱们今天的主角——ThreadLocal。

第3章:ThreadLocal的内部原理

ThreadLocal类的内部结构

ThreadLocal,顾名思义,它是和线程紧密相关的。在Java中,ThreadLocal提供了一种线程局部变量的机制。每个线程都能通过这个ThreadLocal对象存取自己的、独立于其他线程的值。

但ThreadLocal本身并不存储这些值。它更像是一个管理器,它帮助每个线程管理它自己的值。这些值实际上是存储在每个线程自己的ThreadLocalMap中的。

ThreadLocal与Thread的关系

每个Thread对象内部都有一个ThreadLocalMap,这是ThreadLocal的核心所在。这个Map不是Java标准库中的Map,而是ThreadLocal的一个特定实现。它的键是ThreadLocal对象,值是线程局部变量的副本。

当咱们调用ThreadLocal的get或set方法时,实际上是在当前线程的ThreadLocalMap中存取数据。

ThreadLocalMap的工作原理

现在咱们深入一点,看看ThreadLocalMap是怎么工作的。ThreadLocalMap使用线性探测的哈希映射(一种解决哈希冲突的方法)来存储数据。这意味着,当发生哈希冲突时,它会探查下一个可用的槽位来存储键值对。

一个关键的点是,ThreadLocalMap的键(也就是ThreadLocal对象)是弱引用。这意味着,如果外部没有对ThreadLocal对象的强引用,它可能会被垃圾回收器回收。这就引入了内存泄漏的风险,但也提供了一种自动回收无用ThreadLocal对象的机制。

示例:ThreadLocal内部原理的演示

让咱们通过一个简单的例子,来看看ThreadLocal在实际运行中是如何工作的:

public class ThreadLocalInternalExample {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 在主线程中设置值threadLocal.set("主线程的值");new Thread(() -> {// 在子线程中设置不同的值threadLocal.set("子线程的值");printValue();}).start();printValue();}private static void printValue() {// 打印当前线程中的ThreadLocal值System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());}
}

这个例子中,咱们创建了一个ThreadLocal变量,并在主线程和一个子线程中分别设置了不同的值。当咱们调用printValue方法时,它会打印出当前线程中ThreadLocal变量的值。这样,咱们就能清晰地看到,即使是同一个ThreadLocal对象,在不同的线程中也能存储不同的值。

第4章:ThreadLocal使用指南

创建和使用ThreadLocal变量

要使用ThreadLocal,第一步当然是创建一个ThreadLocal对象。ThreadLocal是泛型类,你可以指定它存储的数据类型。比如,要存储字符串,就创建一个ThreadLocal<String>类型的对象。

// 创建一个ThreadLocal对象,用于存储字符串
ThreadLocal<String> threadLocalString = new ThreadLocal<>();

一旦创建了ThreadLocal对象,就可以使用set()get()方法来存储和获取当前线程的局部变量了。

// 在当前线程中设置值
threadLocalString.set("小黑的线程局部变量");// 获取当前线程中的值
String value = threadLocalString.get();
System.out.println(value);  // 输出: 小黑的线程局部变量
示例:在不同线程中存取数据

来看一个实际的例子。假设咱们在一个Web服务器中处理用户请求,每个请求都在自己的线程中处理。咱们可以使用ThreadLocal来存储每个请求的用户ID,这样在整个请求处理过程中,不同的线程就不会互相干扰了。

public class WebServerExample {// 创建一个ThreadLocal对象,用于存储每个线程的用户IDprivate static ThreadLocal<String> userIdThreadLocal = new ThreadLocal<>();public static void main(String[] args) {// 模拟两个用户请求startUserRequest("用户A的ID");startUserRequest("用户B的ID");}private static void startUserRequest(String userId) {new Thread(() -> {// 在当前线程中设置用户IDuserIdThreadLocal.set(userId);// 模拟请求处理processUserRequest();// 清理资源userIdThreadLocal.remove();}).start();}private static void processUserRequest() {// 获取并打印当前线程的用户IDSystem.out.println("处理请求: " + userIdThreadLocal.get());}
}

在这个例子中,咱们创建了一个ThreadLocal对象来存储每个线程的用户ID。这样,每个请求就可以独立地处理,不会干扰到其他请求。

最佳实践与常见误区

在使用ThreadLocal时,有几个最佳实践和常见误区需要注意:

  • 内存泄漏问题:由于ThreadLocal中存储的数据是与线程绑定的,如果线程不死亡,那么这些数据也不会被回收。这可能会导致内存泄漏。解决方法是,在不再需要使用ThreadLocal变量时,调用其remove()方法来清除数据。
  • 初始化:可以通过重写ThreadLocal的initialValue()方法或者使用withInitial(Supplier<? extends S> supplier)方法来提供ThreadLocal变量的初始值。
  • 使用场景:ThreadLocal适合管理线程内部的状态,但如果过度使用,可能会导致代码的可维护性和可读性下降。因此,应当在确实需要隔离线程状态的情况下才使用它。

第5章:ThreadLocal的高级特性与技巧

继承性:InheritableThreadLocal

让咱们先来看看InheritableThreadLocal。这个类是ThreadLocal的一个变体,它的特别之处在于,当一个线程派生出一个子线程时,子线程可以继承父线程中的值。这在某些情况下特别有用,比如在进行请求处理或任务分派时。

来看一个例子:

public class InheritableThreadLocalExample {// 创建一个InheritableThreadLocal对象private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {// 在父线程中设置值inheritableThreadLocal.set("小黑的父线程值");// 创建子线程Thread childThread = new Thread(() -> {// 子线程可以获取父线程设置的值System.out.println("子线程值: " + inheritableThreadLocal.get());});childThread.start();}
}

在这个例子中,咱们在父线程中设置了一个值,然后在子线程中能够获取到这个值。这就是InheritableThreadLocal的魔力所在。

ThreadLocal与内存泄露:防范与诊断

ThreadLocal的一个常见问题是内存泄露。这通常发生在使用线程池的场景中,因为线程池中的线程通常是长期存在的,它们的ThreadLocal变量也不会自动清理,这可能导致内存泄漏。

解决这个问题的一个方法是,每当使用完ThreadLocal变量后,显式地调用remove()方法来清除它:

threadLocal.remove();

这个做法可以确保ThreadLocal变量及时被清除,避免内存泄漏。

性能考量:ThreadLocal的性能影响

虽然ThreadLocal提供了很方便的线程隔离机制,但它也不是没有性能损耗的。在使用ThreadLocal时,尤其是在高并发的环境下,要注意其对性能的影响。

ThreadLocal的性能开销主要来自两个方面:一是ThreadLocalMap的维护,二是ThreadLocal变量的创建和销毁。因此,在使用ThreadLocal时,要尽量重用ThreadLocal变量,避免在高频率的操作中频繁地创建和销毁它们。

第6章:ThreadLocal在Java框架中的应用

在Spring框架中的应用

在Spring框架中,ThreadLocal被用来管理事务和安全上下文。比如,在处理Web请求的过程中,Spring使用ThreadLocal来存储与当前线程相关的事务信息。

这种做法允许开发者在不同的方法和服务之间共享事务上下文,而无需显式地传递这个上下文。这使得代码更加简洁,易于维护。

在并发编程中的应用

在并发编程中,ThreadLocal也是一个非常有用的工具。例如,在使用Executor框架时,咱们可以用ThreadLocal来存储线程的状态或者统计信息。

这是一个简单的例子,演示了如何使用ThreadLocal来追踪每个线程处理的任务数量:

public class ConcurrencyExample {private static ThreadLocal<Integer> taskCount = ThreadLocal.withInitial(() -> 0);public static void main(String[] args) {ExecutorService executor = Executors.newFixedThreadPool(2);for (int i = 0; i < 5; i++) {executor.submit(() -> {int count = taskCount.get();taskCount.set(count + 1);System.out.println("任务数量: " + taskCount.get());});}executor.shutdown();}
}

在这个例子中,咱们使用一个固定大小的线程池来执行任务,并用ThreadLocal来计数每个线程完成的任务数。

其他常见框架中的应用案例

除了Spring和并发编程,ThreadLocal在很多其他的Java框架中也有广泛的应用。例如,在Hibernate或MyBatis这样的ORM框架中,ThreadLocal常被用来存储数据库的会话和事务信息。

这样做的好处是,它使得数据库会话在整个请求处理流程中保持一致,同时又避免了显式地传递这些会话信息。

通过以上的讨论,咱们可以看到,ThreadLocal在Java框架中的应用是非常广泛的。它帮助框架设计者解决了多线程环境下数据共享和隔离的问题,同时也让应用程序的代码更加干净和易于理解。这些都展示了ThreadLocal作为一种工具,在合适的场合下能发挥巨大的作用。

第7章:ThreadLocal的替代方案与比较

ThreadLocal与其他线程封闭技术的比较

在多线程编程中,线程封闭是一个常见的概念。线程封闭指的是对象只能被单个线程访问。ThreadLocal提供了一种线程封闭的实现,但除此之外,还有其他几种实现方式:

  • 局部变量:最简单的线程封闭方式。每个线程调用一个方法时,都会创建这个方法的局部变量副本。
  • 堆栈封闭:类似于局部变量,但用于更复杂的场景,如递归调用。
使用场景与替代技术

虽然ThreadLocal在某些场景下非常有用,但在其他场景中,替代技术可能会更好。比如:

  • 使用并发集合:在需要在多个线程间共享数据时,可以使用Java并发包中提供的线程安全集合,如ConcurrentHashMap
  • 使用锁:对于复杂的同步需求,可以使用锁,比如ReentrantLock
何时应该避免使用ThreadLocal

ThreadLocal虽好,但并不是万能的。在一些情况下,使用ThreadLocal可能并不是最佳选择:

  • 内存泄漏的风险:在使用线程池的情况下,ThreadLocal可能会导致内存泄漏。
  • 性能开销:在高并发环境下,ThreadLocal的使用可能会对性能产生影响。

第8章:总结

ThreadLocal作为一个强大的工具,在多线程环境下解决了很多问题。但正如咱们之前讨论的,它并不是万能的。作为开发者,咱们应该明智地选择适合的工具来解决问题。

咱们要记住的是,技术总是在发展的,咱们也需要不断学习和适应。对ThreadLocal的深入理解,不仅能帮助咱们现在写出更好的代码,也为将来的技术变革做好准备。

好了,今天关于ThreadLocal的探讨就到这里。希望大家都能从中获得有价值的信息,也期待看到大家在实际工作中灵活运用ThreadLocal~

相关文章:

深入理解Java中的ThreadLocal

第1章&#xff1a;引言 大家好&#xff0c;我是小黑。今天咱们来聊聊ThreadLocal。首先&#xff0c;让咱们先搞清楚&#xff0c;ThreadLocal是个什么玩意儿。简单说&#xff0c;ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着&#xff0c;每个线程…...

【重点】【DP】300. 最长递增子序列

题目 更好的方法是耐心排序&#xff0c;参见《算法小抄》的内容&#xff01;&#xff01;&#xff01; 法1&#xff1a;DP 基础解法必须掌握&#xff01;&#xff01;&#xff01; class Solution {public int lengthOfLIS(int[] nums) {if (nums null || nums.length 0) …...

使用freessl为网站获取https证书及配置详细步骤

文章目录 一、进入freessl网站二、修改域名解析记录三、创建证书四、配置证书五、服务启动 一、进入freessl网站 首先进入freessl网站&#xff0c;需要注册一个账号 freessl网站 进入网站后填写自己的域名 接下来要求进行DCV配置 二、修改域名解析记录 到域名管理处编辑域名…...

Java-初识正则表达式 以及 练习

目录 什么是正则表达式&#xff1f; 1. 正则表达式---字符类&#xff08;一个大括号匹配一个字符&#xff09;&#xff1a; 2. 正则表达式---预字符类&#xff08;也是匹配一个字符&#xff09;&#xff1a; 正则表达式---数量词 &#xff08;可以匹配多个字符&#xff09;…...

【Flutter 问题系列第 80 篇】TextField 输入框组件限制可输入的最大长度后,输入的内容中包含表情符号时,获取输入的内容数还是会超出限制的问题

这是【Flutter 问题系列第 80 篇】&#xff0c;如果觉得有用的话&#xff0c;欢迎关注专栏。 博文当前所用 Flutter SDK&#xff1a;3.10.5、Dart SDK&#xff1a;3.0.5 一&#xff1a;问题描述 在输入用户名称、简介等内容时&#xff0c;一般我们都会限制输入框内最大可输入…...

漏洞检测和评估【网站子域扫描工具02】

上一篇&#xff1a;爬取目标网站的域名和子域名【网站子域扫描工具01】 在Python中&#xff0c;有一些流行的漏洞扫描库可以对子域进行漏洞扫描和评估&#xff0c;比如Nmap、Sublist3r等。 1.端口扫描 以下是一个简单的示例代码&#xff0c;展示了如何使用Nmap进行基本的端口扫…...

压力测试+接口测试(工具jmeter)

jmeter是apache公司基于java开发的一款开源压力测试工具&#xff0c;体积小&#xff0c;功能全&#xff0c;使用方便&#xff0c;是一个比较轻量级的测试工具&#xff0c;使用起来非常简单。因 为jmeter是java开发的&#xff0c;所以运行的时候必须先要安装jdk才可以。jmeter是…...

LeetCode 46 全排列

题目描述 全排列 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入…...

npm install 无反应 npm run serve 无反应

说明情况&#xff1a;其实最开始我就是发现我跟着黑马的苍穹外卖的前端day2的环境搭建做的时候&#xff0c;到这一步出现了问题&#xff0c;无论我怎么 npm install 和 npm run serve 都没有像黑马一样有很多东西进行加载&#xff0c;因此我换了一种方法 1.在这个文件夹下cmd …...

JAVAEE初阶 文件IO(二)

文件IO 一. 文件流1.1 字节流 inputStream(1) try with resources方法 1.2 read方法(1) 第一个read方法(2) 第二个read方法(3) read的第三个方法 1.3 字节流 OutoutStream1.4 字符流(1) reader(2) writer 一. 文件流 1.1 字节流 inputStream 在字节流中,我们使用inputStream和…...

Golang 三数之和+ 四数之和 leetcode15、18 双指针法

文章目录 三数之和 leetcode15map记录 失败&#xff01;超出限制双指针法 四数之和 leetcode18 三数之和 leetcode15 知识补充&#xff1a; map的key值必须是可以比较运算的类型&#xff0c;不可以是函数、map、slice map记录 失败&#xff01;超出限制 //得到结果后再去重 失…...

Mysql三种常用的删除方式

前言 在 MySQL 中&#xff0c;有三种常用的方式可以删除表中的数据或整个表&#xff0c;它们分别是 TRUNCATE、DROP 和 DELETE。 TRUNCATE TABLE TRUNCATE TABLE属于DDL语言&#xff0c;不走事务&#xff0c;数据不会回滚 TRUNCATE TABLE 语句会删除表中的所有数据&#xff…...

Eureka 本机集群实现

距离上次发布博客已经一年多了&#xff0c;主要就是因为考研&#xff0c;没时间学习技术的内容&#xff0c;现在有时间继续完成关于代码方面的心得&#xff0c;希望跟大家分享。 今天在做一个 Eureka 的集群实现&#xff0c;我是在本电脑上跑的&#xff0c;感觉这个挺有意思&a…...

查看神经网络中间层特征矩阵及卷积核参数

可视化feature maps以及kernel weights&#xff0c;使用alexnet模型进行演示。 1. 查看中间层特征矩阵 alexnet模型&#xff0c;修改了向前传播 import torch from torch import nn from torch.nn import functional as F# 对花图像数据进行分类 class AlexNet(nn.Module):d…...

重置aws上的ssh默认登录端口

aws上的ec2机器&#xff0c;默认ssh的登录都是22&#xff0c;为了防止被黑&#xff0c;记录下修改该默认端口的方法 修改/etc/ssh/sshd_config文件,将Port 22注释去掉在上面的文件中&#xff0c;加入一行&#xff0c;你想要增加的端口号&#xff0c;格式和22一致注意&#xff1…...

算法刷题——拿出最少数目的魔法豆(力扣)

文章目录 题目描述我的解法思路结果分析 官方题解分析 查漏补缺更新日期参考来源 题目描述 传送门 拿出最少数目的魔法豆&#xff1a;给定一个正整数 数组beans &#xff0c;其中每个整数表示一个袋子里装的魔法豆的数目。请你从每个袋子中拿出 一些豆子&#xff08;也可以 拿…...

Linux消息队列

常用函数 //创建/获取消息队列 int msgget (key_t key, int msgflg); /* key : 为键值,ftok(); msgflg:IPC_CREAT - 创建&#xff0c;不存在即创建&#xff0c;已存在即获取&#xff0c;除非… IPC_EXCL - 排斥&#xff0c;已存在即失败。 */// 向消息队列发送消息 int msgs…...

计算机网络——数据链路层(1)

一、概述 在计算机网络中&#xff0c;数据链路层承担着点对点通信的任务&#xff0c;用于跨物理层在网段节点之间参数数据。它在网络分层中处于物理层之上&#xff0c;网路层之下。 在链路层的讨论中&#xff0c;我们将看到两种截然不同类型的链路层信道。第一种类型是广播信道…...

移动端开发进阶之蓝牙通讯(四)

移动端开发进阶之蓝牙通讯(四) 在移动端开发实践中,可能会要求在不同的设备之间切换,从而提升用户体验; 或者为了提升设备的利用率,实现设备之间的连接和协同工作; 不得不通过多端连接,将多个设备连接在一起,实现设备之间的数据共享、远程控制等功能,根据具体的应用…...

npm换源

检查现在的源地址 npm config get registry 使用淘宝镜像 npm config set registry https://registry.npm.taobao.org 使用官方镜像 npm config set registry https://registry.npmjs.org/...

Spring 中 HttpServletRequest 作为成员变量是安全的吗?

在使用spring框架开发的时候&#xff0c;经常会在controller类中看到 HttpServletRequest 对象参数&#xff0c;一般我们都是直接使用&#xff0c;但是它是何时、怎么注入到 spring 容器的呢 &#xff1f;另外以成员变量注入的 request 是线程安全的吗 ? Controller public c…...

浅聊雷池社区版(WAF)的tengine

雷池社区版是一个开源的免费Web应用防火墙&#xff08;WAF&#xff09;&#xff0c;专为保护Web应用免受各种网络攻击而设计。基于强大的Tengine&#xff0c;雷池社区版提供了一系列先进的安全功能&#xff0c;适用于中小企业和个人用户。 Tengine的故事始于2011年&#xff0c;…...

如何安装配置VisualSVN服务并实现公网访问本地服务【内网穿透】

文章目录 前言1. VisualSVN安装与配置2. VisualSVN Server管理界面配置3. 安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3.4 创建公网地址 4. 固定公网地址访问 前言 SVN 是 subversion 的缩写&#xff0c;是一个开放源代码的版本控制系统…...

解析TZ字样的0时区UTC时间格式化为东八区

带TZ字样的0时区UTC时间格式化为东八区 TZ 的Z是zero timezone 0时区的意思。带TZ的时间是UTC0的时间SimpleDateFormat默认使用系统日历时区&#xff0c;必须手动指定0时区&#xff0c;才能正确解析TZ时间详细测试代码见下&#xff1a; SneakyThrows public static void main…...

python两数之和

给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回…...

PBR材质背光面太暗优化

图形学中漫反射光照遵循兰伯特光照模型&#xff0c;它的公式如下 其中&#xff1a; &#xff1a;漫反射光颜色 &#xff1a;入射光颜色 &#xff1a;材质的漫反射系数 &#xff1a;法线方向 &#xff1a;光源方向 由于背光面的法线方向和光源方向的点积为负数&#xff0c;因此…...

【​电力电子在电力系统中的应用​】6 滞环电流控制的PWM整流器 + STATCOM整流器 + APF仿真

【仅供参考】 【2023.06西南交大电力电子在电力系统中的应用】 目录 步骤一&#xff1a;基于滞环电流控制的PWM整流器仿真 1.1 仿真要求 1.2 仿真电路原理及设计 1.2.1 主电路的搭建 1.2.2 控制电路的搭建 1.3 波形分析 步骤二&#xff1a;从PWM整流器到STATCOM仿真 2…...

接近8000字的SpringSpring常用注解总结!安排

接近8000字的Spring/Spring常用注解总结&#xff01;安排 为什么要写这篇文章&#xff1f; 最近看到网上有一篇关于 SpringBoot 常用注解的文章被转载的比较多&#xff0c;我看了文章内容之后属实觉得质量有点低&#xff0c;并且有点会误导没有太多实际使用经验的人&#xff…...

51单片机_智能家居终端

实物演示效果&#xff1a; https://www.bilibili.com/video/BV1bh4y1A7ZW/?vd_source6ff7cd03af95cd504b60511ef9373a1d 51单片机是否适合做多功能智能家居控制系统&#xff1f;51单片机的芯片是否具有与WiFi通信的能力&#xff1f;如果有的话&#xff0c;具体有哪些芯片啊&a…...

css实现动态水波纹效果

效果如下&#xff1a; 外层容器 (shop_wrap)&#xff1a; 设置外边距 (padding) 提供一些间距和边距 圆形容器 (TheCircle)&#xff1a; 使用相对定位 (position: relative)&#xff0c;宽度和高度均为 180px&#xff0c;形成一个圆形按钮圆角半径 (border-radius) 设置为 50%&…...