轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Java

    • 核心

    • 并发

      • Java并发-线程基础与synchronized关键字
      • Java并发-重入锁ReentrantLock详解与实践
      • Java并发-信号量Semaphore
      • Java并发-读写锁ReadWriteLock
      • Java并发-倒计时器CountDownLatch
      • Java并发-栅栏CyclicBarrier
      • Java并发-LockSupport线程阻塞工具类
      • Java并发-线程池ThreadPoolExecutor
      • Java并发-阻塞队列BlockingQueue
      • Java并发-以空间换时间之ThreadLocal
        • 一、ThreadLocal介绍
          • 1、核心API
          • 2、底层实现原理
          • 3、使用示例:数据库连接管理
        • 二、典型应用场景
          • 1、Web请求性能监控
          • 2、用户上下文传递
          • 3、日期格式化工具
        • 三、Spring框架中的ThreadLocal应用
          • 1、Spring事务管理
          • 2、Security上下文
        • 四、内存泄漏问题与最佳实践
          • 1、内存泄漏原因分析
          • 2、最佳实践
          • 2.1、使用try-finally确保清理
          • 2.2、使用AutoCloseable自动清理
          • 2.3、静态变量vs实例变量
        • 五、性能分析
          • 1、时间复杂度
          • 2、空间复杂度
          • 3、性能对比测试
        • 六、适用场景与限制
          • 1、适用场景
          • 2、不适用场景
        • 七、总结
      • Java并发-无锁策略CAS与atomic包
      • Java并发-JDK并发容器
      • Java并发-异步调用结果之Future和CompletableFuture
      • Java并发-Fork Join框架
      • Java并发-调试与诊断
    • 经验

    • JVM

    • 企业应用

  • Spring

  • 其他语言

  • 工具

  • 后端
  • Java
  • 并发
轩辕李
2021-11-07
目录

Java并发-以空间换时间之ThreadLocal

多线程同时对资源进行访问,为了线程安全,最简单的做法就是使用synchronized关键字,这是一种时间换空间的做法。
在注重性能的场合,多线程竞争问题显得愈发突出。当请求时间变长,用户体验变差,流失率增高。
有没有办法解决这个问题呢?Java提供了一种以空间换时间的容器,它就是ThreadLocal。

ThreadLocal通过为每个线程维护独立的变量副本,避免了线程间的竞争,从而提高了并发性能。这种设计模式在高并发场景下特别有效,因为它完全消除了同步的开销。

# 一、ThreadLocal介绍

ThreadLocal为每个线程维护一份独立的变量副本,每个线程都有自己的ThreadLocalMap来存储数据,从而实现了线程隔离。

# 1、核心API

ThreadLocal提供了以下核心方法:

  • set(T value) - 设置当前线程的线程局部变量的值
  • get() - 返回当前线程所对应的线程局部变量
  • remove() - 删除当前线程所对应的线程局部变量(重要:防止内存泄漏)
  • initialValue() - 返回线程局部变量的初始值(protected方法,可重写)
  • withInitial(Supplier<? extends T> supplier) - Java 8引入的静态工厂方法

# 2、底层实现原理

ThreadLocal的核心在于每个Thread对象都有一个ThreadLocalMap类型的成员变量threadLocals:

public class Thread {
    // 每个线程都有自己的ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // ...
}

ThreadLocalMap是ThreadLocal的静态内部类,它使用线性探测法解决哈希冲突:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);  // key是弱引用
            value = v; // value是强引用
        }
    }
    private Entry[] table;  // 存储数据的数组
}

# 3、使用示例:数据库连接管理

以数据库连接为例,它本身是非线程安全的:

public class TopicDao {
    private Connection connection;  // 线程不安全:多线程共享同一个连接
    
    public void addTopic() throws SQLException {
        Statement statement = connection.createStatement();
        // 多线程环境下会出现并发问题
    }
}

传统方式,要把它变成线程安全,必须使用锁,这会带来性能开销。

使用ThreadLocal的优雅解决方案:

public class TopicDao {
    // 方式1:重写initialValue方法
    private static final ThreadLocal<Connection> connectionHolder = 
        new ThreadLocal<Connection>() {
            @Override
            protected Connection initialValue() {
                try {
                    return DriverManager.getConnection(DB_URL, USER, PASS);
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
        };
    
    // 方式2:使用Java 8的withInitial方法(推荐)
    private static final ThreadLocal<Connection> connectionHolder2 = 
        ThreadLocal.withInitial(() -> {
            try {
                return DriverManager.getConnection(DB_URL, USER, PASS);
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        });
    
    public void addTopic() throws SQLException {
        Connection conn = connectionHolder.get();  // 获取当前线程的连接
        Statement statement = conn.createStatement();
        // 每个线程使用自己的连接,无需同步
    }
    
    public void close() {
        Connection conn = connectionHolder.get();
        if (conn != null) {
            try {
                conn.close();
            } catch (SQLException e) {
                // log error
            }
            connectionHolder.remove();  // 重要:清理ThreadLocal
        }
    }
}

以这种方式运行,程序会为每个线程单独分配一个连接,避免了线程间的竞争。

# 二、典型应用场景

# 1、Web请求性能监控

在Spring中统计URL请求的执行时间,使用拦截器配合ThreadLocal:

public class PerformanceInterceptor extends HandlerInterceptorAdapter {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private static final ThreadLocal<Long> TIME_THREAD_LOCAL = new ThreadLocal<>();

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, 
                           Object handler) {
        TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, 
                         Object handler, ModelAndView modelAndView) {
        Long startTime = TIME_THREAD_LOCAL.get();
        if (startTime != null) {
            long elapsed = System.currentTimeMillis() - startTime;
            logger.info("{} 访问时间为{}ms", request.getRequestURL(), elapsed);
            TIME_THREAD_LOCAL.remove();  // 必须清理,防止内存泄漏
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
                              Object handler, Exception ex) {
        // 确保清理,即使发生异常
        TIME_THREAD_LOCAL.remove();
    }
}

# 2、用户上下文传递

在多层架构中传递用户信息,避免在每个方法中传递参数:

public class UserContext {
    private static final ThreadLocal<User> userHolder = new ThreadLocal<>();
    
    public static void setUser(User user) {
        userHolder.set(user);
    }
    
    public static User getUser() {
        return userHolder.get();
    }
    
    public static void clear() {
        userHolder.remove();
    }
}

// 在过滤器中设置用户信息
public class AuthenticationFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        try {
            User user = authenticate(request);
            UserContext.setUser(user);
            chain.doFilter(request, response);
        } finally {
            UserContext.clear();  // 请求结束时清理
        }
    }
}

// 在业务层直接获取用户信息
public class OrderService {
    public void createOrder(Order order) {
        User currentUser = UserContext.getUser();  // 无需通过参数传递
        order.setUserId(currentUser.getId());
        // ...
    }
}

# 3、日期格式化工具

SimpleDateFormat不是线程安全的,使用ThreadLocal解决:

public class DateFormatUtil {
    // SimpleDateFormat线程不安全,每个线程维护自己的实例
    private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = 
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
    
    public static String format(Date date) {
        return dateFormatHolder.get().format(date);
    }
    
    public static Date parse(String dateStr) throws ParseException {
        return dateFormatHolder.get().parse(dateStr);
    }
}

# 三、Spring框架中的ThreadLocal应用

# 1、Spring事务管理

在Spring的事务管理中,广泛使用ThreadLocal来实现事务的线程隔离。在数据源获取工具DataSourceUtils#doGetConnection方法中:

public static Connection doGetConnection(DataSource dataSource) throws SQLException {
    Assert.notNull(dataSource, "No DataSource specified");
    
    // 从ThreadLocal中获取当前线程的连接
    ConnectionHolder conHolder = (ConnectionHolder) 
        TransactionSynchronizationManager.getResource(dataSource);
        
    if (conHolder != null && (conHolder.hasConnection() || 
                             conHolder.isSynchronizedWithTransaction())) {
        conHolder.requested();
        if (!conHolder.hasConnection()) {
            logger.debug("Fetching resumed JDBC Connection from DataSource");
            conHolder.setConnection(fetchConnection(dataSource));
        }
        return conHolder.getConnection();
    }
    // ... 创建新连接的逻辑
    return con;
}

TransactionSynchronizationManager类使用多个ThreadLocal变量来管理事务状态:

public abstract class TransactionSynchronizationManager {
    // 存储事务资源(如数据库连接)
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");
    
    // 事务同步回调
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");
    
    // 当前事务名称
    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");
    
    // 事务是否只读
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");
    
    // 事务隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");
    
    // 事务是否激活
    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");
}

这种设计使得Spring能够在同一个应用中支持多个并发事务,每个线程维护自己的事务状态,互不干扰。

# 2、Security上下文

Spring Security使用SecurityContextHolder通过ThreadLocal存储安全上下文:

public class SecurityContextHolder {
    private static ThreadLocalSecurityContextHolderStrategy strategy = 
        new ThreadLocalSecurityContextHolderStrategy();
    
    private static final class ThreadLocalSecurityContextHolderStrategy 
            implements SecurityContextHolderStrategy {
        private static final ThreadLocal<SecurityContext> contextHolder = 
            new ThreadLocal<>();
        
        public SecurityContext getContext() {
            SecurityContext ctx = contextHolder.get();
            if (ctx == null) {
                ctx = createEmptyContext();
                contextHolder.set(ctx);
            }
            return ctx;
        }
    }
}

# 四、内存泄漏问题与最佳实践

# 1、内存泄漏原因分析

ThreadLocal可能导致内存泄漏,主要原因是:

  1. 弱引用机制:ThreadLocalMap中的Entry继承自WeakReference<ThreadLocal>,key是弱引用,但value是强引用
  2. 线程池场景:线程池中的线程会被重复使用,如果不清理ThreadLocal,会导致value无法被回收
// 内存泄漏示例
public class MemoryLeakDemo {
    // 线程池复用线程
    private static final ExecutorService executor = Executors.newFixedThreadPool(10);
    
    public static void processBatch() {
        ThreadLocal<byte[]> localStorage = new ThreadLocal<>();
        
        executor.execute(() -> {
            // 分配大对象
            localStorage.set(new byte[1024 * 1024]);  // 1MB
            
            // 业务处理...
            
            // 忘记调用remove(),线程被复用时内存无法释放
            // localStorage.remove();  // 必须调用!
        });
    }
}

# 2、最佳实践

# 2.1、使用try-finally确保清理

public class ThreadLocalBestPractice {
    private static final ThreadLocal<UserInfo> userContext = new ThreadLocal<>();
    
    public void process() {
        try {
            userContext.set(new UserInfo());
            // 业务逻辑
            doBusinessLogic();
        } finally {
            // 确保清理,防止内存泄漏
            userContext.remove();
        }
    }
}

# 2.2、使用AutoCloseable自动清理

public class ThreadLocalResource implements AutoCloseable {
    private final ThreadLocal<Resource> resourceHolder;
    
    public ThreadLocalResource(Resource resource) {
        this.resourceHolder = new ThreadLocal<>();
        this.resourceHolder.set(resource);
    }
    
    public Resource get() {
        return resourceHolder.get();
    }
    
    @Override
    public void close() {
        resourceHolder.remove();
    }
}

// 使用示例
try (ThreadLocalResource resource = new ThreadLocalResource(new Resource())) {
    // 使用资源
    resource.get().doSomething();
}  // 自动调用close()清理

# 2.3、静态变量vs实例变量

// 推荐:使用static final
public class GoodPractice {
    private static final ThreadLocal<DateFormat> formatter = 
        ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
}

// 避免:实例变量可能导致ThreadLocal对象过多
public class BadPractice {
    private ThreadLocal<DateFormat> formatter = new ThreadLocal<>();  // 每个实例一个
}

# 五、性能分析

# 1、时间复杂度

  • get()操作:O(1) 平均情况,最坏O(n)(哈希冲突时线性探测)
  • set()操作:O(1) 平均情况
  • remove()操作:O(1) 平均情况

# 2、空间复杂度

  • 每个线程维护独立的ThreadLocalMap
  • 空间占用 = 线程数 × 每个线程存储的对象大小

# 3、性能对比测试

public class PerformanceComparison {
    private static final int THREAD_COUNT = 100;
    private static final int OPERATIONS = 1000000;
    
    // 同步方式
    private static final Object lock = new Object();
    private static int sharedCounter = 0;
    
    // ThreadLocal方式
    private static final ThreadLocal<Integer> localCounter = 
        ThreadLocal.withInitial(() -> 0);
    
    @Test
    public void testSynchronized() {
        long start = System.currentTimeMillis();
        // 使用synchronized
        synchronized(lock) {
            for (int i = 0; i < OPERATIONS; i++) {
                sharedCounter++;
            }
        }
        System.out.println("Synchronized: " + (System.currentTimeMillis() - start) + "ms");
    }
    
    @Test
    public void testThreadLocal() {
        long start = System.currentTimeMillis();
        // 使用ThreadLocal
        for (int i = 0; i < OPERATIONS; i++) {
            localCounter.set(localCounter.get() + 1);
        }
        System.out.println("ThreadLocal: " + (System.currentTimeMillis() - start) + "ms");
    }
}

# 六、适用场景与限制

# 1、适用场景

  1. 线程间数据隔离:每个线程需要独立的数据副本
  2. 跨层传递数据:避免在方法间传递大量参数
  3. 性能优化:避免同步开销,提高并发性能
  4. 线程不安全对象复用:如SimpleDateFormat、Random等

# 2、不适用场景

  1. 父子线程间共享:ThreadLocal不支持继承(可用InheritableThreadLocal)
  2. 线程间通信:需要线程间共享数据时
  3. 大对象存储:会导致内存占用过高
  4. 短生命周期对象:频繁创建销毁会带来额外开销

# 七、总结

ThreadLocal是Java并发编程中的重要工具,通过为每个线程维护独立的变量副本,实现了线程安全的同时避免了同步开销。它在Spring框架、Web应用、数据库连接管理等场景中有广泛应用。

关键要点:

  1. 原理:每个线程拥有自己的ThreadLocalMap,实现数据隔离
  2. 性能:以空间换时间,避免锁竞争
  3. 注意事项:必须及时调用remove()方法,防止内存泄漏
  4. 最佳实践:使用try-finally或AutoCloseable确保资源清理

掌握ThreadLocal的正确使用方法,能够帮助我们编写出更高效、更优雅的并发程序。

祝你变得更强!

编辑 (opens new window)
#ThreadLocal
上次更新: 2025/08/15
Java并发-阻塞队列BlockingQueue
Java并发-无锁策略CAS与atomic包

← Java并发-阻塞队列BlockingQueue Java并发-无锁策略CAS与atomic包→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式