Java 多线程之 ThreadLocal 的使用

发布时间:2023年12月20日

一、概述

  • 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
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。