07-ThreadLocal有哪些使用场景?【Java面试题总结】

ThreadLocal有哪些使用场景?

7.1 多线程场景下共享变量问题

ThreadLocal是线程本地变量,可以存储共享变量副本,每一个独立线程都有与共享变量一模一样的副本。ThreadLocal在当前线程下共享变量是全局共享的,各个线程之间是相互独立的。

ThreadLocal在多线程场景下解决共享变量问题代码案例:

public class SharedVariableExample {private static ThreadLocal<Integer> sharedVariable = new ThreadLocal<>();public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(3);for (int i = 0; i < 3; i++) {final int value = i; // 保存当前值,确保每个线程的值不同executorService.submit(() -> {sharedVariable.set(value); // 将值设置到ThreadLocal中try {processValue(); // 处理共享变量} finally {sharedVariable.remove(); // 在任务完成后清除ThreadLocal的值}});}executorService.shutdown();}private static void processValue() {int value = sharedVariable.get(); // 从ThreadLocal中获取值System.out.println("Thread " + Thread.currentThread().getName() + ": Value = " + value);}
}

执行结果如下,每个线程都有自己独立的共享变量副本,并且在当前线程下任务一个地方值都是一样的(一个线程下,可能存在多个方法,多个方法即当前线程下共享变量全局共享)

image-20230902140513702

7.2 保存系统上下文信息

在多线程环境中,有时需要在线程之间传递数据,但不希望通过方法参数或全局变量来传递。ThreadLocal可以在当前线程中存储数据,其他线程可以通过ThreadLocal获取该数据。

使用ThreadLocal实现保存上下文信息代码案例如下

新建User类

public class User {private String username;public User(String username) {this.username = username;}public String getUsername() {return username;}
}

新建RequestContext类,用于保存信息到ThreadLocal中

public class RequestContext {private static ThreadLocal<RequestContext> contextHolder = new ThreadLocal<>();private String requestId;private User currentUser;private RequestContext(String requestId, User currentUser) {this.requestId = requestId;this.currentUser = currentUser;}public static void setCurrentContext(String requestId, User currentUser) {RequestContext context = new RequestContext(requestId, currentUser);contextHolder.set(context);}public static RequestContext getCurrentContext() {return contextHolder.get();}public String getRequestId() {return requestId;}public User getCurrentUser() {return currentUser;}
}

新建UserService处理请求

public class UserService {public void processRequest() {RequestContext context = RequestContext.getCurrentContext();String requestId = context.getRequestId();User currentUser = context.getCurrentUser();System.out.println("Processing request: " + requestId + "  ,Current user: " + currentUser.getUsername());}
}

模拟两个请求,分别由不同的两个用户发起的请求。UserService类和RequestContext类本身没有直接的关系,从程序运行结果来看,信息确实从RequestContext透传到了UserService中,说明ThreadLocal起到了中间作用,可以用来保存系统上下文信息。

public class Main {public static void main(String[] args) {// 模拟请求1User user1 = new User("Alice");RequestContext.setCurrentContext("request-1", user1);UserService userService = new UserService();userService.processRequest();// 模拟请求2User user2 = new User("Bob");RequestContext.setCurrentContext("request-2", user2);userService.processRequest();}
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

7.3 管理数据库连接

public class ConnectionManager {private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();public static Connection getConnection() throws SQLException {// 从ThreadLocal获取连接Connection connection = connectionHolder.get();// 如果连接不存在,则创建新连接并保存到ThreadLocalif (connection == null || connection.isClosed()) {connection = createConnection();connectionHolder.set(connection);}return connection;}private static Connection createConnection() throws SQLException {// 创建数据库连接String url = "jdbc:mysql://localhost:3306/dev";String username = "root";String password = "root";return DriverManager.getConnection(url, username, password);}public static void closeConnection() throws SQLException {// 关闭连接并从ThreadLocal中移除Connection connection = connectionHolder.get();if (connection != null && !connection.isClosed()) {connection.close();}connectionHolder.remove();}
}
public class DatabaseService {public void performDatabaseOperation() throws SQLException {Connection connection = ConnectionManager.getConnection();// 使用连接执行数据库操作// ...// 操作完成后关闭连接ConnectionManager.closeConnection();}
}
public class Main {public static void main(String[] args) throws SQLException {// 创建多个线程模拟并发访问Thread thread1 = new Thread(() -> {try {DatabaseService service = new DatabaseService();service.performDatabaseOperation();} catch (SQLException e) {e.printStackTrace();}});Thread thread2 = new Thread(() -> {try {DatabaseService service = new DatabaseService();service.performDatabaseOperation();} catch (SQLException e) {e.printStackTrace();}});// 启动线程thread1.start();thread2.start();}
}

一个请求对应一个数据库连接,一个请求下的所有对数据库的操作都是基于该连接进行的。这样可以在一定程度上避免频繁的创建和销毁数据库连接,从而提高性能。

7.4 基于ThreadLocal实现事务功能

使用ThreadLocal实现事务注解的原理是通过在每个线程中维护一个事务上下文对象,将事务状态与当前线程绑定起来。当需要开启事务时,通过注解或编程方式将事务上下文对象与当前线程进行关联,以便在整个事务执行过程中使用相同的事务上下文对象。

首先,定义一个事务上下文对象,用于存储事务相关的信息,例如事务状态、连接对象等。

public class TransactionContext {private Connection connection;private boolean inTransaction;// 省略构造方法和其他属性的访问方法public Connection getConnection() {return connection;}public void setConnection(Connection connection) {this.connection = connection;}public boolean isInTransaction() {return inTransaction;}public void setInTransaction(boolean inTransaction) {this.inTransaction = inTransaction;}
}

接下来,定义一个事务管理器类,使用ThreadLocal来存储和获取当前线程的事务上下文对象。

package com.spring6.learn.ThreadLocal.test6;import java.sql.Connection;
import java.sql.SQLException;public class TransactionManager {private static ThreadLocal<TransactionContext> transactionContextHolder = new ThreadLocal<>();public static void beginTransaction() {TransactionContext context = new TransactionContext();transactionContextHolder.set(context);context.setInTransaction(true);}public static void commitTransaction() {TransactionContext context = transactionContextHolder.get();if (context != null && context.isInTransaction()) {Connection connection = context.getConnection();try {connection.commit();} catch (SQLException e) {e.printStackTrace();}context.setInTransaction(false);closeConnection(connection);}}public static void rollbackTransaction() {TransactionContext context = transactionContextHolder.get();if (context != null && context.isInTransaction()) {Connection connection = context.getConnection();try {connection.rollback();} catch (SQLException e) {e.printStackTrace();}context.setInTransaction(false);closeConnection(connection);}}public static Connection getCurrentConnection() {TransactionContext context = transactionContextHolder.get();if (context != null) {return context.getConnection();}return null;}public static void setCurrentConnection(Connection connection) {TransactionContext context = transactionContextHolder.get();if (context == null) {context = new TransactionContext();transactionContextHolder.set(context);}context.setConnection(connection);}private static void closeConnection(Connection connection) {try {if (connection != null && !connection.isClosed()) {connection.close();}} catch (SQLException e) {e.printStackTrace();}}
}
public class TransactionManagerTest {private Connection connection;@Beforepublic void setUp() throws SQLException {connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "root");TransactionManager.setCurrentConnection(connection);TransactionManager.beginTransaction();}@Afterpublic void tearDown() throws SQLException {TransactionManager.rollbackTransaction();TransactionManager.setCurrentConnection(null);if (connection != null && !connection.isClosed()) {connection.close();}}@Testpublic void testTransaction() throws SQLException {// 在事务中插入一条数据String insertQuery = "INSERT INTO mytable (name) VALUES (?)";try (PreparedStatement statement = connection.prepareStatement(insertQuery)) {statement.setString(1, "John Doe");statement.executeUpdate();}// 在事务中查询数据String selectQuery = "SELECT COUNT(*) FROM mytable";try (PreparedStatement statement = connection.prepareStatement(selectQuery)) {ResultSet resultSet = statement.executeQuery();if (resultSet.next()) {int count = resultSet.getInt(1);assertEquals(1, count); // 验证插入的数据是否存在}}}
}

TransactionManager.beginTransaction();
}

@After
public void tearDown() throws SQLException {TransactionManager.rollbackTransaction();TransactionManager.setCurrentConnection(null);if (connection != null && !connection.isClosed()) {connection.close();}
}@Test
public void testTransaction() throws SQLException {// 在事务中插入一条数据String insertQuery = "INSERT INTO mytable (name) VALUES (?)";try (PreparedStatement statement = connection.prepareStatement(insertQuery)) {statement.setString(1, "John Doe");statement.executeUpdate();}// 在事务中查询数据String selectQuery = "SELECT COUNT(*) FROM mytable";try (PreparedStatement statement = connection.prepareStatement(selectQuery)) {ResultSet resultSet = statement.executeQuery();if (resultSet.next()) {int count = resultSet.getInt(1);assertEquals(1, count); // 验证插入的数据是否存在}}
}
}

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

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

相关文章

基于Dubbo实现服务的远程调用

目录 前言 RPC思想 为什么使用Dubbo Dubbo技术框架 ​编辑 调用关系流程 基础实现 A.提供统一业务Api B.编辑服务提供者Product B.a 添加依赖 B.b 添加Dubbo 配置(基于yaml配置文件) B.c 编写并暴露服务 C.编辑服务消费者 C.a 添加依赖 C.b 添加Dubbo配置 C.c 引用…

【Redis】3、Redis主从复制、哨兵、集群

Redis主从复制 主从复制&#xff0c;是指将一台Redis服务器的数据&#xff0c;复制到其他的Redis服务器。前者称为主节点(Master)&#xff0c;后者称为从节点(Slave)&#xff1b;数据的复制是单向的&#xff0c;只能由主节点到从节点。 默认情况下&#xff0c;每台Redis服务器…

PostgreSQL安装异常,服务无法启动导致创建服务器超时

win上安装pg后无法创建服务器&#xff0c;提示创建超时&#xff0c;发现服务列表里面pg15服务 并没有启动&#xff0c;启动服务器发现服务不了&#xff0c;截图忘记截了&#xff0c;复现不了&#xff0c;解决方法是 换个身份&#xff0c;然后继续启动&#xff0c;然后就可以在…

如何使用Docker部署Nacos服务?Nacos Docker 快速部署指南: 一站式部署与配置教程

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

说说 TCP的粘包、拆包

分析&回答 拆包和粘包是在socket编程中经常出现的情况&#xff0c; 在socket通讯过程中&#xff0c;如果通讯的一端一次性连续发送多条数据包&#xff0c;tcp协议会将多个数据包打包成一个tcp报文发送出去&#xff0c;这就是所谓的粘包。如果通讯的一端发送的数据包超过一…

【C++漂流记】一文搞懂类与对象的封装

本篇文章主要说明了类与对象中封装的有关知识&#xff0c;包括属性和行为作为整体、访问权限、class与struct的区别、成员属性的私有化&#xff0c;希望这篇文章可以帮助你更好的了解类与对象这方面的知识。 文章目录 一、属性和行为作为整体二、访问权限三、class与struct的区…

某米ax3000路由器组网解析

我们使用某米k60手机与某米ax3000 wifi6路由器组网&#xff0c;来分析和学习网络速率与瓶颈限制。 某米 AX3000 路由器简介 某米 AX3000 路由器是一款支持 WiFi 6 的双频路由器&#xff0c;它的 MIMO 是 22&#xff0c;也就是两根天线。MIMO 是 Multiple Input Multiple Outpu…

复制tr的一行数据或者复制数据使用,使用jq和php

效果图&#xff1a; 2.Html <!--复制的tr数据&#xff0c;s----------------------------------------------------------------------------------------------->{foreach from$arrs keykk itemvv} <tr><td style"text-align:center;" >1</t…

CH341 USB总线转接芯片

产品概述&#xff1a; CH341是一个USB总线的转接芯片&#xff0c;通过USB总线提供异步串口、打印口、并口以及常用的2线和4线等同步串行接口。 在异步串口方式下&#xff0c;CH341提供串口发送使能、串口接收就绪等交互式的速率控制信号以及常用的MODEM联络信号&#xff0c;用于…

日期--data与String的相互转换

首先我们要明确 yyyy-MM-dd HH:mm:ss 其中y:年份 MM:月份 dd:天 HH:小时 mm&#xff1a;分 ss&#xff1a;秒 date转String // 获取当前时间LocalDateTime dateLocalDateTime.now(); // 设置日期格式DateTimeFormatter formatterDateTimeFormatter.ofPattern("yyyy-MM-dd…

PostGreSQL:时间戳时区问题

时间|日期类型 PostGreSQL数据库内置的时间类型如下&#xff0c;注意到&#xff1a;内置的时间类型被分为了with time zone-带时区、without time zone-不带时区两种类型&#xff0c; time、timestamp和interval都可以接受一个可选的精度值 p&#xff08;取值&#xff1a;0-6&a…

JMeter 4.0 如何获取cookie

文章目录 前言JMeter 4.0 如何获取cookie1. 修改jmeter.properties 文件2. 添加HTTP Cookie 管理器3. 获取cookie信息 前言 如果您觉得有用的话&#xff0c;记得给博主点个赞&#xff0c;评论&#xff0c;收藏一键三连啊&#xff0c;写作不易啊^ _ ^。   而且听说点赞的人每天…

如何为虚拟机添加磁盘,扩充原有分区的磁盘空间

如何为虚拟机添加磁盘&#xff0c;扩充原有分区的磁盘空间 关机新增磁盘 虚拟机关机的状态下&#xff0c;在 VMware 当中新增一块磁盘&#xff0c;选中左边要添加磁盘的虚拟机镜像&#xff0c;然后鼠标右键点击设置。 选中磁盘点击添加 点击下一步&#xff0c;悬着SCSI这个…

慕尼黑主题活动!亚马逊云科技生成式AI全新解决方案,引领未来移动出行领域

IAA作为世界五大车展之一&#xff0c;一直对全球汽车产业的发展起着关键作用&#xff01;2023年9月5日在慕尼黑开幕的IAA MOBILITY 2023以“体验联动智慧出行”为主题&#xff0c;紧跟移动出行领域的前沿变化&#xff0c;将汇集整车企业、开发者、供应商、科技公司、服务提供商…

基于深度学习网络的火灾检测算法matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 ................................................................................ load F…

vue: 使用下拉树组件@riophae/vue-treeselect

前言: 在vue中, 因为element-ui 2.X是没有tree-select组件的&#xff0c;到了element-plus就有了 riophae/vue-treeselect是一个基于 Vue.js 的树形选择器组件&#xff0c;可以用于选择树形结构的数据。它支持多选、搜索、异步加载等功能&#xff0c;可以自定义选项的样式和模…

如何制作一个百货小程序

在这个数字化时代&#xff0c;小程序已成为各行各业的必备工具。其中&#xff0c;百货小程序因其便捷性和多功能性&#xff0c;越来越受到人们的青睐。那么&#xff0c;如何制作一个百货小程序呢&#xff1f;下面&#xff0c;我们就详细介绍一下无需编写代码的步骤。 一、进入后…

【C++基础】7. 控制语句

文章目录 【 1. 循环 】1.1 循环类型1.2 循环控制语句break 语句continue 语句goto 语句 1.3 无限循环 【 2. 选择 】switch 语句&#xff1f;&#xff1a;语句 【 1. 循环 】 1.1 循环类型 循环类型描述while 循环当给定条件为真时&#xff0c;重复语句或语句组。它会在执行…

【django开发手册】详解drf filter中DjangoFilterBackend,SearchFilter,OrderingFilter使用方式

&#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是Zeeland&#xff0c;开源建设者与全栈领域优质创作者。&#x1f4dd; CSDN主页&#xff1a;Zeeland&#x1f525;&#x1f4e3; 我的博客&#xff1a;Zeeland&#x1f4da; Github主页: Undertone0809 (Zeeland)&…

如何使用CSS实现一个响应式图片幻灯片(Responsive Image Slider)效果?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 响应式图片幻灯片⭐ HTML结构⭐ CSS样式⭐ JavaScript交互⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…