马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1. ThreadLocal 简介
1.1 什么是 ThreadLocal?
ThreadLocal 是 Java 提供的一个类,它提供了线程本地变量的功能。这些变量与平凡变量差别,每个线程访问 ThreadLocal 变量时,都会有自己独立的、初始化过的变量副本,其他线程无法访问。简而言之,ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
ThreadLocal 的主要特点:
- 每个线程都有自己的变量副本,相互互不影响
- 适合在多线程环境下处理线程安全问题
- 简化了并发编程中的同步操纵
- 适合存储与线程相关的状态信息
1.2 为什么需要 ThreadLocal?
在多线程环境下,我们经常需要处理线程安全问题。通常有两种主要的方式:
- 同步(Synchronization):使用 synchronized 关键字或 Lock 接口实现多线程之间的同步,确保同一时刻只有一个线程能够访问共享资源。
- 线程本地存储(ThreadLocal):为每个线程创建独立的变量副本,避免共享变量,从而规避线程安全问题。
ThreadLocal 适用于以了局景:
- 当某个数据需要被某个线程独享时
- 当某个数据的生命周期与线程的生命周期相同时
- 需要避免线程安全问题,但又不想使用同步机制(因为同步会导致性能开销)时
2. ThreadLocal 的工作原理
2.1 ThreadLocal 的内部布局
ThreadLocal 的工作原理看似复杂,但理解起来并不难。下面是其基本原理:
- Thread 类中有一个成员变量 ThreadLocalMap,它是一个 Map 布局
- ThreadLocalMap 的 key 是 ThreadLocal 对象的弱引用,value 是具体的值
- 当调用 ThreadLocal 的 set(T value) 方法时,会先获取当前线程,然后将值存储在当前线程的 ThreadLocalMap 中
- 当调用 ThreadLocal 的 get() 方法时,会从当前线程的 ThreadLocalMap 中获取值
下面是一个简化的工作原理图:
- Thread 对象
- ├── ThreadLocalMap
- ├── entry1: <ThreadLocal1 引用, 值1>
- ├── entry2: <ThreadLocal2 引用, 值2>
- └── ...
复制代码 2.2 ThreadLocal 的代码实现原理
我们来看看 ThreadLocal 的关键方法实现原理(简化版):
set 方法:
- public void set(T value) {
- // 获取当前线程
- Thread t = Thread.currentThread();
- // 获取当前线程的 ThreadLocalMap
- ThreadLocalMap map = getMap(t);
- // 如果 map 存在,则直接设置值
- if (map != null)
- map.set(this, value);
- else
- // 否则创建 map 并设置值
- createMap(t, value);
- }
复制代码 get 方法:
- public T get() {
- // 获取当前线程
- Thread t = Thread.currentThread();
- // 获取当前线程的 ThreadLocalMap
- ThreadLocalMap map = getMap(t);
- // 如果 map 存在
- if (map != null) {
- // 获取与当前 ThreadLocal 对象关联的 Entry
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- // 返回值
- T result = (T)e.value;
- return result;
- }
- }
- // 如果 map 不存在或 entry 不存在,则返回初始值
- return setInitialValue();
- }
复制代码 remove 方法:
- public void remove() {
- // 获取当前线程的 ThreadLocalMap
- ThreadLocalMap m = getMap(Thread.currentThread());
- // 如果 map 存在,则从中删除当前 ThreadLocal 对应的 entry
- if (m != null)
- m.remove(this);
- }
复制代码 3. ThreadLocal 的使用方法
3.1 创建 ThreadLocal 对象
创建 ThreadLocal 对象非常简单,只需使用泛型指定存储的数据范例:
- // 创建一个存储 Integer 类型的 ThreadLocal
- ThreadLocal<Integer> threadLocalInt = new ThreadLocal<>();
- // 创建一个存储 String 类型的 ThreadLocal
- ThreadLocal<String> threadLocalString = new ThreadLocal<>();
- // 创建一个存储自定义对象类型的 ThreadLocal
- ThreadLocal<User> threadLocalUser = new ThreadLocal<>();
复制代码 3.2 设置初始值
ThreadLocal 提供了两种设置初始值的方式:
方式一:重写 initialValue 方法
- ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
- @Override
- protected Integer initialValue() {
- return 0; // 设置默认值为 0
- }
- };
复制代码 方式二:使用 withInitial 静态方法(Java 8 及以上)
- ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
复制代码 方式三:在初次使用前设置值
- ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
- threadLocal.set(0);
复制代码 3.3 基本操纵:get、set、remove
ThreadLocal 有三个核心方法:
- set(T value):设置当前线程的线程局部变量值
- get():获取当前线程的线程局部变量值
- remove():移除当前线程的线程局部变量
示例:
- public class ThreadLocalExample {
- // 创建一个 ThreadLocal 对象
- private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
- public static void main(String[] args) {
- // 主线程设置值
- threadLocal.set("Main Thread Value");
-
- // 获取值
- System.out.println("Main Thread: " + threadLocal.get());
-
- // 创建一个新线程
- Thread thread = new Thread(() -> {
- // 新线程中获取值(初始为 null,因为每个线程都有独立的副本)
- System.out.println("New Thread Initially: " + threadLocal.get());
-
- // 新线程设置自己的值
- threadLocal.set("New Thread Value");
-
- // 再次获取值
- System.out.println("New Thread After Setting: " + threadLocal.get());
-
- // 移除值
- threadLocal.remove();
-
- // 移除后再获取值(应该为 null 或初始值)
- System.out.println("New Thread After Removal: " + threadLocal.get());
- });
-
- thread.start();
-
- try {
- thread.join(); // 等待新线程执行完成
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- // 主线程的值不受新线程影响
- System.out.println("Main Thread Again: " + threadLocal.get());
-
- // 最后,主线程也要移除值,防止内存泄漏
- threadLocal.remove();
- }
- }
复制代码 输出示例:
- Main Thread: Main Thread Value
- New Thread Initially: null
- New Thread After Setting: New Thread Value
- New Thread After Removal: null
- Main Thread Again: Main Thread Value
复制代码 3.4 使用 InheritableThreadLocal
如果希望子线程能继承父线程的 ThreadLocal 变量,可以使用 InheritableThreadLocal:
- public class InheritableThreadLocalExample {
- // 创建一个 InheritableThreadLocal 对象
- private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
- public static void main(String[] args) {
- // 主线程设置值
- inheritableThreadLocal.set("Main Thread Value");
-
- // 创建一个新线程
- Thread thread = new Thread(() -> {
- // 新线程继承了父线程的值
- System.out.println("New Thread: " + inheritableThreadLocal.get());
-
- // 修改值不会影响父线程
- inheritableThreadLocal.set("New Thread Modified Value");
- System.out.println("New Thread Modified: " + inheritableThreadLocal.get());
- });
-
- thread.start();
-
- try {
- thread.join(); // 等待新线程执行完成
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- // 主线程的值不受子线程修改的影响
- System.out.println("Main Thread Again: " + inheritableThreadLocal.get());
-
- // 最后,移除值
- inheritableThreadLocal.remove();
- }
- }
复制代码 输出示例:
- New Thread: Main Thread Value
- New Thread Modified: New Thread Modified Value
- Main Thread Again: Main Thread Value
复制代码 4. ThreadLocal 的应用场景
ThreadLocal 在现实开发中有很多应用场景,下面是一些常见的例子:
4.1 在多线程环境下保存用户信息
在 Web 应用中,通常需要在整个哀求处理过程中转达用户信息(如用户ID、用户名等)。使用 ThreadLocal 可以避免在每个方法中都转达用户参数。
- public class UserContext {
- private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
-
- public static void setUser(User user) {
- userThreadLocal.set(user);
- }
-
- public static User getUser() {
- return userThreadLocal.get();
- }
-
- public static void clear() {
- userThreadLocal.remove();
- }
- }
- // 使用示例
- public class UserService {
- public void processUser(String userId) {
- // 从数据库获取用户
- User user = getUserFromDB(userId);
-
- // 设置到 ThreadLocal
- UserContext.setUser(user);
-
- // 其他方法可以直接获取用户信息,无需传参
- businessMethod1();
- businessMethod2();
-
- // 操作完成后清理 ThreadLocal
- UserContext.clear();
- }
-
- private void businessMethod1() {
- // 直接获取用户信息
- User user = UserContext.getUser();
- System.out.println("Business Method 1 for user: " + user.getName());
- }
-
- private void businessMethod2() {
- // 直接获取用户信息
- User user = UserContext.getUser();
- System.out.println("Business Method 2 for user: " + user.getName());
- }
-
- private User getUserFromDB(String userId) {
- // 模拟从数据库获取用户
- return new User(userId, "User" + userId);
- }
- }
- class User {
- private String id;
- private String name;
-
- public User(String id, String name) {
- this.id = id;
- this.name = name;
- }
-
- public String getId() {
- return id;
- }
-
- public String getName() {
- return name;
- }
- }
复制代码 4.2 数据库毗连管理
ThreadLocal 可以用于管理数据库毗连,为每个线程提供独立的数据库毗连,避免多线程争用同一毗连导致的问题。
- public class ConnectionManager {
- private static final ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {
- try {
- return DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
- } catch (SQLException e) {
- throw new RuntimeException("创建数据库连接失败", e);
- }
- });
-
- public static Connection getConnection() {
- return connectionHolder.get();
- }
-
- public static void closeConnection() {
- Connection conn = connectionHolder.get();
- if (conn != null) {
- try {
- conn.close();
- } catch (SQLException e) {
- // 处理关闭连接异常
- }
- }
- connectionHolder.remove(); // 不要忘记移除
- }
- }
- // 使用示例
- public class DatabaseService {
- public void performDatabaseOperations() {
- try {
- // 获取当前线程的数据库连接
- Connection conn = ConnectionManager.getConnection();
-
- // 使用连接执行 SQL 操作
- try (Statement stmt = conn.createStatement()) {
- // 执行查询
- ResultSet rs = stmt.executeQuery("SELECT * FROM users");
- while (rs.next()) {
- System.out.println("User: " + rs.getString("username"));
- }
- }
- } catch (SQLException e) {
- e.printStackTrace();
- } finally {
- // 操作完成后关闭并清除连接
- ConnectionManager.closeConnection();
- }
- }
- }
复制代码 4.3 简化参数转达
ThreadLocal 可以在调用链中转达参数,避免在每个方法中都转达相同的参数。
- public class RequestContext {
- private static final ThreadLocal<RequestData> requestThreadLocal = new ThreadLocal<>();
-
- public static void setRequestData(RequestData requestData) {
- requestThreadLocal.set(requestData);
- }
-
- public static RequestData getRequestData() {
- return requestThreadLocal.get();
- }
-
- public static void clear() {
- requestThreadLocal.remove();
- }
- }
- class RequestData {
- private String requestId;
- private String clientIp;
- private String userAgent;
-
- // 构造函数、getter和setter省略...
- }
- // 使用示例
- public class RequestProcessor {
- public void processRequest(HttpRequest request) {
- // 解析请求信息
- RequestData requestData = new RequestData();
- requestData.setRequestId(generateRequestId());
- requestData.setClientIp(request.getClientIp());
- requestData.setUserAgent(request.getUserAgent());
-
- // 设置到 ThreadLocal
- RequestContext.setRequestData(requestData);
-
- try {
- // 处理请求的各个阶段
- validateRequest();
- authenticateUser();
- processBusinessLogic();
- generateResponse();
- } finally {
- // 清理 ThreadLocal
- RequestContext.clear();
- }
- }
-
- private void validateRequest() {
- RequestData data = RequestContext.getRequestData();
- System.out.println("Validating request: " + data.getRequestId());
- // 验证逻辑...
- }
-
- private void authenticateUser() {
- RequestData data = RequestContext.getRequestData();
- System.out.println("Authenticating user for request: " + data.getRequestId());
- // 认证逻辑...
- }
-
- private void processBusinessLogic() {
- RequestData data = RequestContext.getRequestData();
- System.out.println("Processing business logic for request: " + data.getRequestId());
- // 业务逻辑...
- }
-
- private void generateResponse() {
- RequestData data = RequestContext.getRequestData();
- System.out.println("Generating response for request: " + data.getRequestId());
- // 生成响应...
- }
-
- private String generateRequestId() {
- return UUID.randomUUID().toString();
- }
- }
复制代码 4.4 事务管理
在涉及事务的应用中,ThreadLocal 可以用于跟踪和管理事务状态。
- public class TransactionManager {
- private static final ThreadLocal<Transaction> transactionThreadLocal = new ThreadLocal<>();
-
- public static void beginTransaction() {
- Transaction transaction = new Transaction();
- transaction.begin();
- transactionThreadLocal.set(transaction);
- }
-
- public static Transaction getCurrentTransaction() {
- return transactionThreadLocal.get();
- }
-
- public static void commitTransaction() {
- Transaction transaction = transactionThreadLocal.get();
- if (transaction != null) {
- transaction.commit();
- transactionThreadLocal.remove();
- }
- }
-
- public static void rollbackTransaction() {
- Transaction transaction = transactionThreadLocal.get();
- if (transaction != null) {
- transaction.rollback();
- transactionThreadLocal.remove();
- }
- }
- }
- class Transaction {
- private String id;
-
- public Transaction() {
- this.id = UUID.randomUUID().toString();
- }
-
- public void begin() {
- System.out.println("Transaction " + id + " started");
- }
-
- public void commit() {
- System.out.println("Transaction " + id + " committed");
- }
-
- public void rollback() {
- System.out.println("Transaction " + id + " rolled back");
- }
- }
- // 使用示例
- public class TransactionExample {
- public void performBusinessOperation() {
- try {
- // 开始事务
- TransactionManager.beginTransaction();
-
- // 执行数据库操作 1
- updateTableA();
-
- // 执行数据库操作 2
- updateTableB();
-
- // 提交事务
- TransactionManager.commitTransaction();
- } catch (Exception e) {
- // 发生异常,回滚事务
- TransactionManager.rollbackTransaction();
- throw e;
- }
- }
-
- private void updateTableA() {
- System.out.println("Updating Table A in transaction: " +
- TransactionManager.getCurrentTransaction().id);
- // 更新逻辑...
- }
-
- private void updateTableB() {
- System.out.println("Updating Table B in transaction: " +
- TransactionManager.getCurrentTransaction().id);
- // 更新逻辑...
- }
- }
复制代码 5. ThreadLocal 的内存泄漏问题
5.1 内存泄漏的缘故起因
ThreadLocal 使用不当可能导致内存泄漏,主要缘故起因有两点:
- ThreadLocalMap 的 Entry 是弱引用:ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,这意味着当没有强引用指向 ThreadLocal 变量时,它会被垃圾采取。但是,对应的 value 是强引用,如果没有手动删除,就无法被采取。
- 线程池中的线程生命周期很长:在使用线程池的场景下,线程的生命周期可能很长,甚至与应用程序的生命周期一样长。如果不整理 ThreadLocal 变量,那么这些变量会随着线程一直存在于内存中。
下面是一个可能导致内存泄漏的示例:
- public class ThreadLocalMemoryLeakExample {
- public static void main(String[] args) {
- ExecutorService executor = Executors.newFixedThreadPool(10);
-
- for (int i = 0; i < 100; i++) {
- executor.execute(new LeakyTask());
- }
-
- executor.shutdown();
- }
-
- static class LeakyTask implements Runnable {
- // 创建一个 ThreadLocal 变量
- private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
-
- @Override
- public void run() {
- // 分配一个大对象到 ThreadLocal 变量
- threadLocal.set(new byte[1024 * 1024]); // 分配 1MB 大小的数组
-
- // 执行一些操作...
-
- // 没有调用 threadLocal.remove(),可能导致内存泄漏
- }
- }
- }
复制代码 在上面的例子中,我们创建了一个线程池,并提交了多个任务。每个任务都将一个大对象存储在 ThreadLocal 中,但没有在任务结束时移除该对象。由于线程池中的线程会被重用,这些大对象将一直存在于内存中,导致内存泄漏。
5.2 避免内存泄漏的方法
要避免 ThreadLocal 引起的内存泄漏,应该遵照以下原则:
- 在不需要 ThreadLocal 变量时调用 remove() 方法:
- try {
- threadLocal.set(value);
- // 使用 threadLocal...
- } finally {
- threadLocal.remove(); // 确保清理
- }
复制代码 - 使用 try-with-resources 和自界说的 ThreadLocal 资源:
- public class ThreadLocalScope<T> implements AutoCloseable {
- private final ThreadLocal<T> threadLocal;
-
- public ThreadLocalScope(ThreadLocal<T> threadLocal, T value) {
- this.threadLocal = threadLocal;
- threadLocal.set(value);
- }
-
- @Override
- public void close() {
- threadLocal.remove();
- }
- }
- // 使用示例
- ThreadLocal<String> threadLocal = new ThreadLocal<>();
- try (ThreadLocalScope<String> scope = new ThreadLocalScope<>(threadLocal, "value")) {
- // 使用 threadLocal...
- } // 自动调用 close() 方法,清理 ThreadLocal
复制代码 - 使用第三方库提供的工具类:一些库(如 Spring)提供了整理 ThreadLocal 变量的工具类。
下面是一个修正后的线程池示例:
- public class ThreadLocalSafeExample {
- public static void main(String[] args) {
- ExecutorService executor = Executors.newFixedThreadPool(10);
-
- for (int i = 0; i < 100; i++) {
- executor.execute(new SafeTask());
- }
-
- executor.shutdown();
- }
-
- static class SafeTask implements Runnable {
- // 创建一个 ThreadLocal 变量
- private static final ThreadLocal<byte[]> threadLocal = new ThreadLocal<>();
-
- @Override
- public void run() {
- try {
- // 分配一个大对象到 ThreadLocal 变量
- threadLocal.set(new byte[1024 * 1024]); // 分配 1MB 大小的数组
-
- // 执行一些操作...
-
- } finally {
- // 确保清理 ThreadLocal 变量
- threadLocal.remove();
- }
- }
- }
- }
复制代码 6. ThreadLocal 的最佳实践
6.1 何时使用 ThreadLocal
ThreadLocal 并不是解决所有多线程问题的全能药。以下是一些适合使用 ThreadLocal 的场景:
- 线程隔离的场景:每个线程需要有自己的实例
- 跨函数转达数据:避免通过参数转达数据
- 线程安全场景:替代 synchronized 来确保线程安全
不适合使用 ThreadLocal 的场景:
- 共享数据:如果需要线程之间共享数据,ThreadLocal 不是好的选择
- 生命周期差别等:如果变量的生命周期与线程的生命周期差别等
- 频仍创建和销毁线程的场景:可能导致性能问题
6.2 ThreadLocal 使用的注意事项
- 总是在 finally 块中调用 remove 方法:
- try {
- threadLocal.set(value);
- // 使用 threadLocal...
- } finally {
- threadLocal.remove();
- }
复制代码 - 为 ThreadLocal 变量使用 private static final 修饰符:
- private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
复制代码 - 优先使用初始化器:尽量使用初始化器设置初始值,避免 NPE(空指针异常):
- private static final ThreadLocal<User> userThreadLocal = ThreadLocal.withInitial(() -> new User());
复制代码 - 不要在线程池中直接使用不可变 ThreadLocal:线程池中的线程是重用的,以是要确保 ThreadLocal 变量在每次任务结束后都被整理。
- 避免将 ThreadLocal 变量设置为 null:应该使用 remove() 方法而不是 set(null)。
- ThreadLocal 变量通常是静态的:ThreadLocal 变量通常声明为静态变量,这样可以确保多个线程访问同一个 ThreadLocal 实例。
6.3 ThreadLocal 工具类示例
下面是一个综合的 ThreadLocal 工具类示例,它遵照了最佳实践:
- /**
- * ThreadLocal 工具类,提供安全的 ThreadLocal 使用方式
- */
- public class ThreadLocalContext<T> {
- private final ThreadLocal<T> threadLocal;
-
- /**
- * 创建一个没有初始值的 ThreadLocalContext
- */
- public ThreadLocalContext() {
- this.threadLocal = new ThreadLocal<>();
- }
-
- /**
- * 创建一个带有初始值提供者的 ThreadLocalContext
- */
- public ThreadLocalContext(Supplier<? extends T> supplier) {
- this.threadLocal = ThreadLocal.withInitial(supplier);
- }
-
- /**
- * 获取当前线程的变量值
- */
- public T get() {
- return threadLocal.get();
- }
-
- /**
- * 设置当前线程的变量值
- */
- public void set(T value) {
- threadLocal.set(value);
- }
-
- /**
- * 移除当前线程的变量值
- */
- public void remove() {
- threadLocal.remove();
- }
-
- /**
- * 使用资源并自动清理
- */
- public <R> R withValue(T value, Supplier<R> supplier) {
- set(value);
- try {
- return supplier.get();
- } finally {
- remove();
- }
- }
-
- /**
- * 执行操作并自动清理
- */
- public void withValue(T value, Runnable runnable) {
- set(value);
- try {
- runnable.run();
- } finally {
- remove();
- }
- }
- }
- // 使用示例
- public class ThreadLocalContextExample {
- // 创建用户上下文
- private static final ThreadLocalContext<User> userContext = new ThreadLocalContext<>();
-
- public void processUserRequest(String userId) {
- // 从数据库获取用户
- User user = getUserFromDB(userId);
-
- // 使用 withValue 方法确保自动清理
- userContext.withValue(user, () -> {
- // 处理用户请求
- businessMethod1();
- businessMethod2();
- });
- }
-
- private void businessMethod1() {
- User user = userContext.get();
- System.out.println("Business Method 1 for user: " + user.getName());
- }
-
- private void businessMethod2() {
- User user = userContext.get();
- System.out.println("Business Method 2 for user: " + user.getName());
- }
-
- private User getUserFromDB(String userId) {
- // 模拟从数据库获取用户
- return new User(userId, "User" + userId);
- }
- }
复制代码 7. ThreadLocal 与框架集成
很多流行的 Java 框架都使用 ThreadLocal 来管理线程相关的上下文信息。了解这些框架中的 ThreadLocal 使用方式可以资助你更好地使用和调试它们。
7.1 Spring 框架中的 ThreadLocal
Spring 框架在多个地方使用了 ThreadLocal:
- 哀求上下文:RequestContextHolder 使用 ThreadLocal 存储当前哀求的 ServletRequestAttributes
- 事务管理:TransactionSynchronizationManager 使用多个 ThreadLocal 变量管理事务资源
- 安全上下文:Spring Security 的 SecurityContextHolder 默认使用 ThreadLocal 存储认证信息
一个 Spring MVC 应用中使用 ThreadLocal 的示例:
- @RestController
- public class UserController {
-
- @GetMapping("/current-user")
- public String getCurrentUser() {
- // 从 Spring Security 上下文中获取当前用户
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- if (authentication != null && authentication.isAuthenticated()) {
- return "Current user: " + authentication.getName();
- }
- return "No authenticated user";
- }
-
- @GetMapping("/current-request")
- public String getCurrentRequest() {
- // 从 RequestContextHolder 中获取当前请求
- ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
- if (attributes != null) {
- HttpServletRequest request = attributes.getRequest();
- return "Current request URL: " + request.getRequestURL();
- }
- return "No current request";
- }
- }
复制代码 7.2 Hibernate 中的 ThreadLocal
Hibernate 使用 ThreadLocal 管理当前会话:
- public class HibernateExample {
- private static final SessionFactory sessionFactory; // 假设已经初始化
-
- public void doWithSession() {
- Session session = null;
- Transaction tx = null;
-
- try {
- // 获取当前线程的会话
- session = sessionFactory.getCurrentSession();
-
- // 开始事务
- tx = session.beginTransaction();
-
- // 执行数据库操作
- User user = new User("john", "John Doe");
- session.save(user);
-
- // 提交事务
- tx.commit();
- } catch (Exception e) {
- if (tx != null) {
- tx.rollback();
- }
- throw e;
- }
- // 注意:不需要关闭 session,因为 getCurrentSession() 会自动管理
- }
- }
复制代码 7.3 Log4j/Logback 中的 MDC
日记框架中的 MDC (Mapped Diagnostic Context) 使用 ThreadLocal 来存储日记上下文信息:
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.slf4j.MDC;
- public class LoggingExample {
- private static final Logger logger = LoggerFactory.getLogger(LoggingExample.class);
-
- public void processRequest(String requestId, String userId) {
- // 将请求 ID 和用户 ID 添加到 MDC
- MDC.put("requestId", requestId);
- MDC.put("userId", userId);
-
- try {
- // 日志
会自动包含 MDC 中的信息 - logger.info("开始处理请求");
-
- // 业务处理...
- businessLogic();
-
- logger.info("请求处理完成");
- } finally {
- // 清理 MDC
- MDC.clear();
- }
- }
-
- private void businessLogic() {
- // 这里的日志
也会包含 MDC 信息 - logger.info("执行业务逻辑");
- }
- }
复制代码 设置日记格式:
- <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
- <encoder>
- <pattern>%d{HH:mm:ss.SSS} [%thread] [requestId=%X{requestId}] [userId=%X{userId}] %-5level %logger{36} - %msg%n</pattern>
- </encoder>
- </appender>
复制代码 输出示例:
- 10:15:23.456 [main] [requestId=abc123] [userId=user456] INFO LoggingExample - 开始处理请求
- 10:15:23.500 [main] [requestId=abc123] [userId=user456] INFO LoggingExample - 执行业务逻辑
- 10:15:23.550 [main] [requestId=abc123] [userId=user456] INFO LoggingExample - 请求处理完成
复制代码 8. ThreadLocal 的高级主题
8.1 ThreadLocal 与线程池的联合使用
在使用线程池时,需要特别注意整理 ThreadLocal 变量,否则可能导致意外举动或内存泄漏。
一种常见的做法是使用装饰模式包装 Runnable 或 Callable,确保在任务执行前后正确设置和整理 ThreadLocal 变量:
- public class ThreadLocalAwareRunnable implements Runnable {
- private final Runnable delegate;
- private final Map<ThreadLocal<?>, Object> threadLocalValues;
-
- public ThreadLocalAwareRunnable(Runnable delegate, Map<ThreadLocal<?>, Object> threadLocalValues) {
- this.delegate = delegate;
- this.threadLocalValues = new HashMap<>(threadLocalValues);
- }
-
- @Override
- public void run() {
- // 保存当前线程的 ThreadLocal 值
- Map<ThreadLocal<?>, Object> previousValues = new HashMap<>();
- for (Map.Entry<ThreadLocal<?>, Object> entry : threadLocalValues.entrySet()) {
- ThreadLocal<Object> threadLocal = (ThreadLocal<Object>) entry.getKey();
- previousValues.put(threadLocal, threadLocal.get());
- threadLocal.set(entry.getValue());
- }
-
- try {
- // 执行原始任务
- delegate.run();
- } finally {
- // 恢复原来的 ThreadLocal 值
- for (Map.Entry<ThreadLocal<?>, Object> entry : threadLocalValues.entrySet()) {
- ThreadLocal<Object> threadLocal = (ThreadLocal<Object>) entry.getKey();
- Object previousValue = previousValues.get(threadLocal);
- if (previousValue != null) {
- threadLocal.set(previousValue);
- } else {
- threadLocal.remove();
- }
- }
- }
- }
- }
- // 使用示例
- public class ThreadPoolThreadLocalExample {
- private static final ThreadLocal<String> userThreadLocal = new ThreadLocal<>();
-
- public static void main(String[] args) {
- ExecutorService executor = Executors.newFixedThreadPool(5);
-
- // 设置主线程的 ThreadLocal 值
- userThreadLocal.set("MainUser");
-
- // 创建 ThreadLocal 值映射
- Map<ThreadLocal<?>, Object> threadLocalValues = new HashMap<>();
- threadLocalValues.put(userThreadLocal, "TaskUser");
-
- // 提交装饰后的任务
- executor.execute(new ThreadLocalAwareRunnable(() -> {
- System.out.println("User in task: " + userThreadLocal.get());
- }, threadLocalValues));
-
- executor.shutdown();
- }
- }
复制代码 8.2 ThreadLocal 的性能考虑
ThreadLocal 虽然可以避免同步开销,但它也有自己的性能特点:
- 空间开销:每个线程都有自己的副本,如果存储大对象且线程数目多,会消耗更多内存。
- 哈希查找开销:ThreadLocalMap 是基于开放地点法的哈希表,查找也需要一定的时间。
- 初始化开销:如果使用 initialValue 方法或 withInitial 方法,每个线程初次访问时都会执行初始化逻辑。
性能优化发起:
- 减少 ThreadLocal 的数目:合并相关的变量到一个上下文对象中,减少 ThreadLocal 实例的数目。
- 避免存储大对象:如果需要存储大对象,考虑只存储引用或标识符。
- 懒加载大对象:针对大对象,使用懒加载方式:
- private static final ThreadLocal<ExpensiveObject> expensiveObjectThreadLocal =
- ThreadLocal.withInitial(() -> null);
- public static ExpensiveObject getExpensiveObject() {
- ExpensiveObject object = expensiveObjectThreadLocal.get();
- if (object == null) {
- object = createExpensiveObject();
- expensiveObjectThreadLocal.set(object);
- }
- return object;
- }
复制代码 8.3 使用 ThreadLocal.ThreadLocalMap 的高级用法
ThreadLocal.ThreadLocalMap 是 ThreadLocal 内部使用的数据布局,通常不直接使用。但是,在某些高级场景下,可能需要自界说类似的机制:
- public class CustomThreadLocalMap<K, V> {
- private final ThreadLocal<Map<K, V>> threadLocal = ThreadLocal.withInitial(HashMap::new);
-
- public V get(K key) {
- return threadLocal.get().get(key);
- }
-
- public void put(K key, V value) {
- threadLocal.get().put(key, value);
- }
-
- public void remove(K key) {
- threadLocal.get().remove(key);
- }
-
- public boolean containsKey(K key) {
- return threadLocal.get().containsKey(key);
- }
-
- public void clear() {
- threadLocal.get().clear();
- }
-
- public Set<K> keySet() {
- return threadLocal.get().keySet();
- }
-
- public Collection<V> values() {
- return threadLocal.get().values();
- }
-
- public Set<Map.Entry<K, V>> entrySet() {
- return threadLocal.get().entrySet();
- }
-
- public void removeThreadLocalMap() {
- threadLocal.remove();
- }
- }
- // 使用示例
- public class CustomThreadLocalMapExample {
- private static final CustomThreadLocalMap<String, Object> contextMap = new CustomThreadLocalMap<>();
-
- public static void main(String[] args) {
- // 设置值
- contextMap.put("userId", "user123");
- contextMap.put("requestId", "req456");
-
- // 获取值
- String userId = (String) contextMap.get("userId");
- System.out.println("User ID: " + userId);
-
- // 遍历所有值
- for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
- System.out.println(entry.getKey() + ": " + entry.getValue());
- }
-
- // 清理
- contextMap.clear();
- // 或者完全移除
- contextMap.removeThreadLocalMap();
- }
- }
复制代码 9. 总结
ThreadLocal 是 Java 多线程编程中的紧张工具,它为每个线程提供独立的变量副本,解决了特定范例的并发问题。本文具体先容了 ThreadLocal 的使用方法、工作原理、应用场景以及潜在的内存泄漏问题及其解决方案。
9.1 主要要点回顾
- ThreadLocal 的核心功能:为每个线程提供变量的独立副本
- 常用方法:set()、get()、remove()、withInitial()
- 常见应用场景:用户上下文、数据库毗连管理、事务管理等
- 内存泄漏:由于 ThreadLocalMap 的 Entry 是弱引用,未整理的值可能导致内存泄漏
- 最佳实践:总是在 finally 块中调用 remove(),使用静态 final 修饰符,优先使用初始化器
9.2 何时选择 ThreadLocal
- 当你需要在同一个线程的多个方法之间转达变量
- 当你需要避免在参数中转达上下文对象
- 当你需要线程安全但又不想使用同步机制
- 当变量的生命周期与线程的生命周期相似
9.3 何时避免使用 ThreadLocal
- 当变量需要在线程之间共享
- 在高频创建和销毁线程的场景
- 存储生命周期与线程差别等的对象
正确使用 ThreadLocal 可以简化代码,提高性能,但也需要注意潜在的风险。希望本文能资助你在 Java 多线程编程中更好地使用 ThreadLocal。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|