ThreadLocal应用场景
一、会话用户信息管理
在一个使用 Web 技术的应用中,通常需要管理用户的会话状态。每个用户请求都在一个独立的线程中处理,但每个线程都需要访问用户的会话数据。
- 案例如下:
public class UserSessionManager {
private static ThreadLocal<UserSession> userSessionThreadLocal = new ThreadLocal<>();
public static void setUserSession(UserSession userSession) {
userSessionThreadLocal.set(userSession);
}
public static UserSession getUserSession() {
return userSessionThreadLocal.get();
}
public static void clearUserSession() {
userSessionThreadLocal.remove();
}
}
在上面的示例中,UserSessionManager
类使用 ThreadLocal
来管理用户会话数据。当用户登录时、或登陆后(主要是登陆后、从token中解析用户信息,从而放到threadlocal中),可以将用户会话数据设置到 ThreadLocal
中:
UserSession userSession = new UserSession(userId, userName);
UserSessionManager.setUserSession(userSession);
jdbc_37">二、数据库连接管理-jdbc
ThreadLocal
来在每个线程中存储数据库连接,从而确保每个线程都使用自己的连接,避免线程间的竞争和资源管理问题。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcUtils {
// 数据库驱动名和数据库URL
private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String URL = "jdbc:mysql://localhost:3306/mysql-student?serverTimezone=UTC";
// 数据库用户名和密码
private static final String USERNAME = "root";
private static final String PASSWORD = "root";
// 使用 ThreadLocal 来维护数据库连接的线程隔离
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
// 获取数据库连接
public static Connection getConnection() throws SQLException {
// 先从 ThreadLocal 中获取连接
Connection connection = connectionHolder.get();
// 如果没有连接,则创建一个新连接,并将其保存到 ThreadLocal 中
if (connection == null) {
connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
connectionHolder.set(connection);
}
return connection;
}
// 释放数据库连接
public static void releaseConnection() {
Connection connection = connectionHolder.get();
try {
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
// 清空 ThreadLocal 中的连接
connectionHolder.remove();
}
}
// 执行 SQL 查询语句
public static ResultSet executeQuery(String sql, Object... params) throws SQLException {
PreparedStatement ps = getConnection().prepareStatement(sql);
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
return ps.executeQuery();
}
// 执行 SQL 更新语句
public static int executeUpdate(String sql, Object... params) throws SQLException {
PreparedStatement ps = getConnection().prepareStatement(sql);
if (params != null && params.length > 0) {
for (int i = 0; i < params.length; i++) {
ps.setObject(i + 1, params[i]);
}
}
return ps.executeUpdate();
}
public static void main(String[] args) throws SQLException {
System.out.println(getConnection());
System.out.println(getConnection()==getConnection());
}
}
三、用户请求上下文
在 Web 应用中,可以使用 ThreadLocal
来存储用户请求的上下文信息,如请求的参数、请求的路径等。这样在处理请求时,可以方便地获取这些信息,而不需要将它们传递到每个方法中。
java">public class UserRequestContext {
private static ThreadLocal<Map<String, String>> contextThreadLocal = ThreadLocal.withInitial(HashMap::new);
public static void setAttribute(String key, String value) {
contextThreadLocal.get().put(key, value);
}
public static String getAttribute(String key) {
return contextThreadLocal.get().get(key);
}
public static void clear() {
contextThreadLocal.get().clear();
}
}
// 在请求处理过程中
UserRequestContext.setAttribute("userId", "123");
UserRequestContext.setAttribute("username", "john");
// 在不同的方法中访问
String userId = UserRequestContext.getAttribute("userId");
String username = UserRequestContext.getAttribute("username");
四、线程池中的任务隔离
在使用线程池执行任务时,如果每个任务需要独立的环境或状态,可以使用 ThreadLocal
来确保每个任务都具有自己的状态,而不会相互干扰。
java">public class TaskContext {
private static ThreadLocal<String> taskIdThreadLocal = new ThreadLocal<>();
public static void setTaskId(String taskId) {
taskIdThreadLocal.set(taskId);
}
public static String getTaskId() {
return taskIdThreadLocal.get();
}
public static void clear() {
taskIdThreadLocal.remove();
}
}
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int taskId = i;
executorService.submit(() -> {
TaskContext.setTaskId("Task-" + taskId);
System.out.println("Executing task: " + TaskContext.getTaskId());
// 在任务中访问任务特定的上下文信息
TaskContext.clear();
});
}
executorService.shutdown();
五、日志跟踪
略。常见就1-3~