文章目录
- 一、概述
- 二、使用方法
- 三、测试示例
- 四、应用示例
- 五、在 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();} }