Java 多线程之 ThreadLocal 的使用

文章目录

    • 一、概述
    • 二、使用方法
    • 三、测试示例
    • 四、应用示例
    • 五、在 Spring 源码中应用

一、概述

  • ThreadLocal 用于在多线程环境中维护线程封闭(thread-local)的变量。线程封闭是一种确保变量在多线程环境中的线程安全性的机制,每个线程都有自己独立的变量副本,互不干扰。ThreadLocal 提供了一种简单的方式来实现线程封闭,它为每个线程都创建了一个独立的变量副本。

  • 使用场景

    • 保存线程私有数据:当多个线程需要访问某个数据,但每个线程都需要有自己的数据副本时,可以使用 ThreadLocal 。
    • 避免传递参数:通过 ThreadLocal,可以避免在方法调用间频繁传递参数,特别是在一些框架或库中,例如线程池。
  • 注意事项

    • 在使用完 ThreadLocal 后,要注意及时调用 remove 方法,以避免内存泄漏。
    • ThreadLocal 不解决共享数据的线程安全问题,仅提供每个线程独立的副本,因此在并发场景下仍需注意数据的一致性和安全性。

二、使用方法

  • 主要方法说明

    • void set(T value) 用于将当前线程的线程局部变量设置为指定值。
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.set("Some Value");
    
    • T get() 获取当前线程的线程局部变量的值。
      • 如果当前线程之前没有调用 set 方法设置过值,那么 get 将返回 null
    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    String value = threadLocal.get();
    
    • void remove() 移除当前线程的线程局部变量。

      • 移除后,如果再次调用 get 方法,将返回 null

      • 通常在线程结束时或者线程池中线程重用之前调用,以避免内存泄漏。

    ThreadLocal<String> threadLocal = new ThreadLocal<>();
    threadLocal.remove();
    

三、测试示例

  • 使用 ThreadLocal threadLocal = new ThreadLocal<>() 声明一个 threadLocal 对象,然后在任意线程中都可以使用 threadLocal 对象。通过测试发现,每线程中通过 threadLocal.set 保存的值都只能在当前线程中使用。

    public class ThreadLocalExample {// 创建一个 ThreadLocal 变量private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 在主线程中设置 ThreadLocal 变量的值threadLocal.set("主线程 ThreadLocal");// 创建并启动两个线程Thread thread1 = new Thread(() -> {// 在线程1中设置 ThreadLocal 变量的值threadLocal.set("线程 1 ThreadLocal");printThreadLocalValue(); // 输出线程1的值});Thread thread2 = new Thread(() -> {// 在线程2中设置 ThreadLocal 变量的值threadLocal.set("线程 2 ThreadLocal");printThreadLocalValue(); // 输出线程2的值});thread1.start();thread2.start();// 在主线程中获取 ThreadLocal 变量的值printThreadLocalValue(); // 输出主线程的值}private static void printThreadLocalValue() {// 获取当前线程的 ThreadLocal 变量值System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());}
    }
    

四、应用示例

  • 如下创建一个线程安全的数据库连接管理类 DatabaseConnectionManager,然后在其他线程中就可以安全的使用数据库连接了。

        public static class DatabaseConnectionManager {// 使用 ThreadLocal 来存储数据库连接private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {try {// 创建数据库连接,实例应用中,这里可以从连接池中获取。return DriverManager.getConnection("jdbc:mysql://192.168.8.70:3306/mysql", "root", "123456");} catch (SQLException e) {throw new RuntimeException("不能创建数据连接", e);}});// 获取当前线程的数据库连接public static Connection getConnection() {return connectionHolder.get();}// 关闭当前线程的数据库连接public static void closeConnection() {try {Connection connection = connectionHolder.get();if (connection != null && !connection.isClosed()) {connection.close();}} catch (SQLException e) {// 处理异常e.printStackTrace();} finally {// 清除 ThreadLocal 中的值,避免内存泄漏connectionHolder.remove();}}}
    
  • 完整测试示例

    package top.yiqifu.study.p021_limit;import java.sql.*;// 包装类
    public class Test113_ThreadLocalDB {public static void main(String[] args) {// 模拟多个线程使用数据库连接for (int i = 1; i <= 5; i++) {Thread thread = new Thread(new DatabaseTask("线程" + i));thread.start();}}// 模拟数据库操作的任务private static class DatabaseTask implements Runnable {private final String threadName;public DatabaseTask(String threadName) {this.threadName = threadName;}@Overridepublic void run() {try {// 获取数据库连接Connection connection = DatabaseConnectionManager.getConnection();System.out.println(threadName + ": 获取数据库连接");// 模拟数据库操作Statement statement = connection.createStatement();ResultSet resultSet = statement.executeQuery("select * from sys.sys_config");if(resultSet.next()){System.out.println(threadName + ": 获取数据库数据"+ resultSet.getString(1));}resultSet.close();statement.close();// 关闭数据库连接DatabaseConnectionManager.closeConnection();System.out.println(threadName + ": 关闭数据库连接");} catch (Exception e) {e.printStackTrace();}}}public static class DatabaseConnectionManager {// 使用 ThreadLocal 来存储数据库连接private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {try {// 创建数据库连接return DriverManager.getConnection("jdbc:mysql://192.168.8.70:3306/mysql", "root", "yiqifu");} catch (SQLException e) {throw new RuntimeException("不能创建数据连接", e);}});// 获取当前线程的数据库连接public static Connection getConnection() {return connectionHolder.get();}// 关闭当前线程的数据库连接public static void closeConnection() {try {Connection connection = connectionHolder.get();if (connection != null && !connection.isClosed()) {connection.close();}} catch (SQLException e) {// 处理异常e.printStackTrace();} finally {// 清除 ThreadLocal 中的值,避免内存泄漏connectionHolder.remove();}}}
    }

    注意如果是 MySQL 5.7,则需要在 pom.xml 添加

         <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.49</version></dependency>
    

五、在 Spring 源码中应用

  • 在 Spring MVC 源码(DispatcherServlet)中使用 ThreadLocal 的示例

    public class DispatcherServlet extends FrameworkServlet {private static final ThreadLocal<HttpServletRequest> requestThreadLocal = new ThreadLocal<>();@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {try {// 将当前请求对象存储到 ThreadLocal 中requestThreadLocal.set(request);// 执行具体的请求处理逻辑super.doService(request, response);} finally {// 清除 ThreadLocal 中的值,避免内存泄漏requestThreadLocal.remove();}}// 在其他地方可以通过此方法获取当前线程的 HttpServletRequestpublic static HttpServletRequest getCurrentRequest() {return requestThreadLocal.get();}
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/234986.shtml

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

相关文章

3-高可用-隔离术

隔离是指将系统或资源分割开&#xff0c;系统隔离是为了在系统发生故障时&#xff0c;能限定传播范围和影响范围&#xff0c;即发生故障后不会出现滚雪球效应&#xff0c;从而保证只有出问题的服务不可用&#xff0c;其他服务还是可用的。 比较多的隔离手段有线程隔离、进程隔…

Java--包,访问修饰符,多态数组,==和equals,hashcode,toString

包 同一个包里面不能有重复的类&#xff0c;不同的包可以有相同的类&#xff0c;包和包之间互不干涉。一个包下面有很多的类。 包的命名规则&#xff1a; 只能包含数字&#xff0c;字母&#xff0c;下划线&#xff0c;小圆点&#xff0c;但不能用数字开头&#xff0c;不能是关…

2_js运算符与流程控制语句

1. 运算符的应用 1.1 算数运算符 浮点数的精度问题 浮点数值的最高精度是17位小数&#xff0c;不要直接判断两个浮点数是否相等。 var result 0.1 0.2; // 结果不是 0.3&#xff0c;而是&#xff1a;0.30000000000000004 console.log(0.07 * 100); // 结果不是 7&#…

C++学习笔记(十五)

继承 继承是面向对象三大特性之一 有些类与类之间存在特殊的关系&#xff0c;例如下图中&#xff1a; 我们发现&#xff0c;定义这些类时&#xff0c;下级别的成员除了拥有上一级的共性&#xff0c;还有自己的特性。 这个时候我们就可以考虑利用继承的技术&#xff0c;减少重…

3 - Electron app BrowserWindow对象-关于窗口

优雅的打开应用~ 当加载缓慢&#xff0c;打开应用的一瞬间会出现白屏&#xff0c;以下方法可以解决 const mainWindow new BrowserWindow({ show: false }) mainWindow.once(ready-to-show, () > {mainWindow.show() }) 设置背景颜色 const win new BrowserWindow({ b…

对数器的作用(找bug)

冒泡排序 package class02;public class Code_Comp_BubbleSort {public static void bubbleSort(int[] arr) { // 冒泡排序错误写法if (arr.length < 2) {return;}for (int i 0; i < arr.length; i) {for (int j 0; j < arr.length - 1; j) {if (arr[j] < arr[j…

MongoDB的原子操作findAndReplace、findOneAndDelete和deleteMany

本文主要介绍MongoDB的原子操作findAndReplace、findOneAndDelete和deleteMany。 目录 MongoDB的原子操作一、findAndReplace二、findOneAndDelete三、deleteMany MongoDB的原子操作 MongoDB的原子操作指的是在单个操作中对数据库的数据进行读取和修改&#xff0c;并确保操作是…

ALVR 编译 windows android [Streamer Client]

ALVR下载源码地址&#xff1a;ALVR download 1 ALVR windows编译 [Streamer Building] 1.1 环境安装 参照官方文档&#xff1a;https://github.com/alvr-org/ALVR/wiki/Building-From-Source&#xff0c; 1 rust环境安装&#xff1a; 下载并安装https://static.rust-lang.…

selenium css定位

selenium-css定位 element_css driver.find_element(By.CSS_SELECTOR, css表达式)css定位说明 selenium中的css定位&#xff0c;实际是通过css选择器来定位到具体元素&#xff0c;css选择器来自于css语法 css定位优点 语法简洁对比其他定位方式&#xff0c;定位效率更快对…

高防服务器防御靠谱吗?

​  随着互联网的普及和信息技术的不断发展&#xff0c;网络安全问题日益突出。高防服务器作为一种专业的网络安全设备&#xff0c;在防御网络攻击方面扮演着越来越重要的角色。然而&#xff0c;高防服务器是否靠谱&#xff0c;是否能够有效地防御各种网络攻击&#xff0c;一…

CUMT--Java--JDBC编程

目录 一、JDBC简介 二、数据库访问 1、加载数据库驱动 2、建立数据连接 3、创建Statement对象 4、执行SQL语句 5、访问结果集 三、MetaData接口 1、DatabaseMetaData接口 2、ResultSetMetaData接口 四、事务 1、JDBC中的事务 2、保存点 3、批量更新 一、JDBC简…

【JAVA】重力反弹,反弹高次一次比一次低

本来是想实现泡泡屏保(javascript实现漂亮的气泡碰撞效果(Chrome浏览器下更佳) 下载-脚本之家)的&#xff0c;还未实现 import javax.swing.*; import java.awt.*; import java.util.LinkedList; import java.util.Random;class Bubble {public static Image image;public int…

轮滑加盟培训机构管理系统源码开发方案

一、项目背景与目标 &#xff08;一&#xff09;项目背景 随着轮滑运动的普及和市场需求的增加&#xff0c;轮滑加盟培训机构逐渐兴起。这些机构面临着学员管理、课程排班、教师管理等多方面的挑战。为了提高管理效率和服务质量&#xff0c;需要开发一套专门针对轮滑加盟培训…

FormData文件上传多文件上传

一、简介 ​ 通常情况下&#xff0c;前端在使用post请求提交数据的时候&#xff0c;请求都是采用application/json 或 application/x-www-form-urlencoded编码类型&#xff0c;分别是借助JSON字符串来传递参数或者keyvalue格式字符串&#xff08;多参数通过&进行连接&#…

大数据分析22、23真题回忆

2022 学长描述 1. 一个很简单的据估计 2. 算一个决策树 3. Cypher图 4.Hadoop和Spark的区别 2023 真题回忆 1. 大数据分析的定义 说出大数据分析三个层次 2.大数据分析流程 预处理部分包含哪几个步骤 3.Spark核心部件和应用库有哪些 并简要说明功能 4. 主成分分析和因…

HTML5+CSS3小实例:纯CSS实现锚点平滑过渡

实例:纯CSS实现锚点平滑过渡 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"&…

【无语】Microsoft Edge 浏览器不显示后台返回的数值数据

Microsoft Edge 禁用 JSON 视图 写在前面禁用 JSON 视图 写在前面 遇到一个有意思的事情&#xff0c;在用 Microsoft Edge 浏览器发送请求测试时发现&#xff0c;后端返回的数值数据没有正常展示&#xff0c;而是类似查看源码的结果&#xff0c;只显示了一个行号1&#xff0c;…

最优化理论复习--对偶单纯形方法及灵敏度分析

对偶单纯形方法 定义&#xff1a;设 x ( 0 ) x^{(0)} x(0) 是(L)问题的基本解&#xff08;不一定是可行解&#xff08;极点&#xff09;&#xff09;&#xff0c;如果它的对偶问题的解释可行的&#xff0c;则称 x ( 0 ) x^{(0)} x(0) 为原问题的对偶可行基本解 从而衍生出结…

《哥德尔证明》阅读笔记——初等命题逻辑的一致性证明过程

前言 前两节主要阐述了公理系统的发展历史&#xff0c;一致性问题的提出&#xff0c;以及希尔伯特的洞见&#xff0c;本节将给出哥德尔证明所需的最后一次具体背景阐述&#xff0c;包含两个问题&#xff1a;一是罗素所著的《数学原理》是为何而写&#xff1f;二是从数学原理中…

【算法面经】九维数据CV算法工程师一面

来源&#xff1a;投稿 作者&#xff1a;LSC 编辑&#xff1a;学姐 1.详细聊项目 2.模型的常见优化方法 (1)更换模型backbone等结构 (2)数据准确 (3)平衡数据解决样本不均衡 (4)模型集成等 (5)根据loss下降的趋势调整参数 3.介绍一下Transformer的结构&#xff0c;推导一…