【Java后端】之 ThreadLocal 详解

news/2024/10/24 20:28:36/

想象一下,你有一个工具箱,里面放着各种工具。在多人共用这个工具箱的时候,很容易出现混乱,比如有人拿走了你的锤子,或者你找不到合适的螺丝刀。为了避免这种情况,最好的办法就是每个人都有自己独立的工具箱。

Java 的 ThreadLocal 就相当于给每个线程提供了一个这样的“私有小盒子”。每个线程都可以把自己的东西放进去,不用担心被其他线程干扰。

1. 为什么要用 ThreadLocal

在多线程编程中,经常会遇到多个线程同时访问共享变量的情况。如果没有做好同步控制,就可能会出现数据不一致的问题,也就是所谓的“线程安全问题”。

ThreadLocal 提供了一种解决线程安全问题的方法,它让每个线程都拥有自己的变量副本,避免了共享变量的竞争。

2. ThreadLocal 怎么工作的?

ThreadLocal 并不是真的给每个线程创建了一个独立的变量,而是通过一个巧妙的设计来实现的。

每个线程内部都有一个 ThreadLocalMap,可以把它看作是一个键值对的集合。ThreadLocal 对象本身作为键,而线程的私有变量作为值。

线程调用 ThreadLocal.get() 方法时,ThreadLocal 会根据当前线程找到对应的 ThreadLocalMap,然后根据自身作为键取出对应的值。这样就实现了每个线程访问自己私有变量的目的。

3. 如何使用 ThreadLocal

使用 ThreadLocal 非常简单,通常分为三步:

  • 创建 ThreadLocal 对象: 就像创建一个普通的对象一样,例如 ThreadLocal<String> userName = new ThreadLocal<>();,这里 String 表示私有变量的类型。

  • 设置值: 使用 userName.set("张三"); 方法,把“张三”这个字符串放到当前线程的“小盒子”里。

  • 获取值: 使用 String name = userName.get(); 方法,从当前线程的“小盒子”里取出值。

ThreadLocal 的 常用方法:

public API描述
set(T)设置当前线程的副本
T get()获取当前线程的副本
void remove()移除当前线程的副本
ThreadLocal<S> withInitial(Supplier<S>)创建 ThreadLocal 并指定缺省值创建工厂
protected API描述
T initialValue()设置缺省值

4. 举个栗子

假设一个 Web 应用,每个用户请求都会由一个独立的线程处理。我们可以使用 ThreadLocal 来存储用户的登录信息:

java">private static final ThreadLocal<String> USER_ID = new ThreadLocal<>();public void processRequest(String userId) {USER_ID.set(userId); // 将用户 ID 存储到 ThreadLocal 中// ... 处理请求 ...String currentUserId = USER_ID.get(); // 获取当前线程的用户 ID// ...USER_ID.remove(); // 使用完毕后清除值
}

5. 内存泄漏的坑

ThreadLocal 使用不当可能会导致内存泄漏。这是因为 ThreadLocalMap 中的键是弱引用,而值是强引用。如果 ThreadLocal 对象被垃圾回收了,但是线程还在运行,那么 ThreadLocalMap 中的值就无法被回收,导致内存泄漏。

为了避免这种情况,最好在使用完 ThreadLocal 后手动调用 remove() 方法清除值,就像上面的例子一样。

6. InheritableThreadLocal:子线程也能继承“小盒子”

InheritableThreadLocal 可以让子线程继承父线程的“小盒子”。也就是说,子线程可以访问父线程设置的线程局部变量。

7. 总结

ThreadLocal 就像给每个线程提供了一个私有的“小盒子”,可以用来存储线程私有的数据,避免线程安全问题。使用起来很简单,但是要注意内存泄漏的坑,记得用完后调用 remove() 方法清理。 选择使用 ThreadLocal 还是其他同步机制,需要根据具体情况进行权衡。 如果只是简单的共享数据,同步机制可能更简单直接。 如果需要维护每个线程独立的数据副本,ThreadLocal 则是更好的选择。

希望这个更通俗易懂的解释能够帮助你理解 ThreadLocal。下期见,谢谢~


http://www.ppmy.cn/news/1541656.html

相关文章

编译器与集成开发环境

编译器 一.什么是编译器 将高级语言代码转换成CPU能够识别的二进制指令 二.常用编译器 1.MSVC Windows平台Visual Studio(VS)自带的C/C编译器 2.MingW(编译环境)&#xff1a;gcc是MingW的核心组成 Linux/Windows 3.clang 苹果电脑上的开发工具&#xff0c;XCode 集成…

(A-D)AtCoder Beginner Contest 376

目录 比赛链接&#xff1a; A - Candy Button 题目链接&#xff1a; 题目描述&#xff1a; 数据范围&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 样例解释&#xff1a; 分析&#xff1a; 代码&#xff1a; B - Hands on Ring (Easy) 题目链接&#xff1…

D. Deleting Divisors

传送门&#xff1a;Problem - D - Codeforces 题意&#xff1a; 思路&#xff1a;博弈论 打表找规律&#xff08; 递推 &#xff09; 如果 ans[i] 为 true &#xff0c;则 Alice 能赢 ans[i] 为 false&#xff0c;则 Bob 会赢 数字 n 的一个因子 为 x &#xff0c; 如果 …

第10节 arkTS GridRow

在 ArkTS 中&#xff0c; GridRow 是 Grid 布局系统中的一个重要组成部分&#xff0c;用于定义网格布局中的行。以下是关于 GridRow 的详细介绍&#xff1a; 基本概念 Grid 布局将容器划分为行和列的网格结构&#xff0c; GridRow 则负责确定每行的属性和布局方式。…

2374. 边积分最高的节点

2374. 边积分最高的节点 题目链接&#xff1a;2374. 边积分最高的节点 代码如下&#xff1a; class Solution { public:int edgeScore(vector<int>& edges){int res 0;vector<long long> scores(edges.size());for (int i 0; i < edges.size(); i){int…

SSM义工活动系统-计算机毕业设计源码86760

目录 1 绪论 1.1 研究背景 1.2研究意义 1.3论文结构与章节安排 2 义工活动系统分析 2.1 可行性分析 2.2 系统功能分析 2.3 系统用例分析 2.4 系统流程分析 2.5本章小结 3 义工活动系统总体设计 3.1 系统功能模块设计 3.2 数据库设计 3.4本章小结 4 义工活动系统详…

数据结构和算法的常见面试题

关注B站可以观看更多实战教学视频&#xff1a;hallo128的个人空间 数据结构和算法的常见面试题 数据结构和算法是技术面试中的重点&#xff0c;尤其在软件工程师岗位。以下是一些常见的面试题&#xff0c;涵盖了不同的数据结构和算法&#xff1a; 一、数组与字符串 找出数组…

build bootable grub iso image hard disk

一、pre-work 1. 安装grub-install grub-mkrescue命令 apt install gub2-common grub-pc grub-efi-ia32 grub-efi-amd64:i386 grub-efi-amd64 二、iso image 1. bios iso #!/bin/shmkdir bios_iso mkdir -p bios_iso/boot/grubcp grub.cfg bios_iso/boot/grub/grub-mk…