Java 多线程之 ThreadLocal 的使用
2023-12-20 18:59:31
一、概述
-
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; } @Override public 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<>(); @Override protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception { try { // 将当前请求对象存储到 ThreadLocal 中 requestThreadLocal.set(request); // 执行具体的请求处理逻辑 super.doService(request, response); } finally { // 清除 ThreadLocal 中的值,避免内存泄漏 requestThreadLocal.remove(); } } // 在其他地方可以通过此方法获取当前线程的 HttpServletRequest public static HttpServletRequest getCurrentRequest() { return requestThreadLocal.get(); } }
文章来源:https://blog.csdn.net/qifu123/article/details/135113948
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!