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.hqwc.cn/news/98788.html

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

相关文章

Unity2D实现左右移动的敌人角色

文章目录 环境与角色创建敌人角色 敌人脚本检测前方是否有地面获取检测点检测地面 完整代码运行结果其他文章 环境与角色 创建敌人角色 简单起见&#xff0c;突出脚本的内容&#xff0c;我们就只创建一个圆形用来当做当前的敌人角色。 为分清左右&#xff0c;我们再为敌人角色…

Docker使用数据卷挂载进行数据存储与共享

一、挂载和数据卷 在 Docker 中&#xff0c;挂载&#xff08;Mounting&#xff09;和数据卷&#xff08;Data Volumes&#xff09;是用于在容器和宿主机之间共享数据的机制。 挂载&#xff1a;将宿主机文件系统中的目录或文件与容器中的目录或文件进行关联的过程。数据卷&…

Jenkins java8安装版本安装

一、首先准备Jenkins、Jdk8、Tomcat9安装包 根据Jenkins官网介绍&#xff0c;Jenkins支持Java8的版本如下&#xff1a; 我们选择2.164版本进行安装&#xff0c;根据版本号支持输入下载地址&#xff1a;https://archives.jenkins.io/war/2.164/jenkins.war&#xff0c;进行下载…

软件测试|Python自动化测试实现的思路

Python自动化测试常用于Web应用、移动应用、桌面应用等的测试 Python自动化实现思路通常分为以下几步&#xff1a; 1. 确定自动化测试的范围和目标&#xff1a; 首先需要明确需要进行自动化测试的范围和目标&#xff0c;包括测试场景、测试用例、测试数据等。 2. 选择自动化…

【知网检索稳定】第三届社会发展与媒体传播国际学术会议(SDMC 2023)

第三届社会发展与媒体传播国际学术会议&#xff08;SDMC 2023&#xff09; 2023 3rd International Conference on Social Development and Media Communication 第三届社会发展与媒体传播国际学术会议 (SDMC 2023)将于2023年11月03-05日在中国杭州召开。会议主题主要围绕社会…

博物学欣赏

自文艺复兴以降&#xff0c;西方开启发现世界的旅程。 这些东西对于科学、地理学、考古学、探险、旅游学、博物学、绘画学、美学无疑有着至高无上的借鉴价值。我们今天出版这些图文并茂的书籍有如斯高远的志向和目标&#xff1a; 展现自然的历史风貌 呈现万物的生态原样 复现…

Unittest自动化测试框架vs Pytest自动化测试框架

引言   前面一篇文章Python单元测试框架介绍已经介绍了python单元测试框架&#xff0c;大家平时经常使用的是unittest&#xff0c;因为它比较基础&#xff0c;并且可以进行二次开发&#xff0c;如果你的开发水平很高&#xff0c;集成开发自动化测试平台也是可以的。而这篇文章…

Java作业3

1.下面代码的运行结果是&#xff08;C&#xff09; public static void main(String[] args){String s;System.out.println("s"s);}A.代码编程成功&#xff0c;并输出”s” B.代码编译成功&#xff0c;并输出”snull” C.由于String s没有初始化&#xff0c;代码不…

微信小程序 通过响应式数据控制元素class属性

我想大家照这个和我最初的目的一样 希望有和vue中v-bind:class一样方便的指令 但答案不太尽人意 这里 我们只能采用 三元运算符的形式 参考代码如下 <view class"item {{ userId item.userId ? isThisUser : }}"> </view>这里 我们判断 如果当前ite…

SSRF漏洞复现(redis)

文章目录 启动环境漏洞复现探测存活IP和端口服务计划任务反弹shell 前提条件&#xff1a; 1.安装docker docker pull medicean/vulapps:j_joomla_22.安装docker-compose docker run -d -p 8000:80 medicean/vulapps:j_joomla_23.下载vulhub 安装环境已完成&#xff0c;故此省略…

小狐狸ChatGPT付费创作系统V2.2.4独立版 +WEB端+ H5端 + 小程序端(免授权去后门弹窗)安装教程

播播资源提供的小狐狸ChatGPT付费创作系统V2.2.4相比上一版h5适配普通手机浏览器&#xff0c;gpt4的接口openai改成自定义&#xff0c;以支持更多三方接口。因绘画效果不好&#xff0c;移出绘画接口openai和replicate&#xff0c;AI参数设置处的openai改成自定义&#xff0c;pc…

Android 1.2 开发环境搭建

目录 1.2 开发环境搭建 1.JDK安装与配置 2.开发工具二选一 3.相关术语的解析 4.ADB命令行的一些指令 5.APP程序打包与安装的流程&#xff1a; 6.APP的安装过程&#xff1a; 7.本节小结 1.2 开发环境搭建 现在主流的Android开发环境有: ①Eclipse ADT SDK ②Android Stu…