Java 中使用 ThreadLocal 的最佳实践是什么?
Java 中使用 ThreadLocal 的最佳实践是什么?
回答重点
1)不要滥用 ThreadLocal:
ThreadLocal适用于需要为每个线程维护独立副本的场景,例如:数据库连接、用户会话、事务上下文、临时缓存等。- 对于能通过参数传递的上下文信息,不应该使用
ThreadLocal来处理,避免设计不合理和代码可读性差的问题。
2)避免内存泄漏:
ThreadLocal中的key是弱引用,但value是强引用,因此需要在适当的时机调用remove()方法来清除ThreadLocal的值,避免内存泄漏。尤其是在使用线程池时,线程对象会被重用,若不手动清理,容易导致内存泄漏。
3)使用静态变量存放 ThreadLocal:
- 将
ThreadLocal作为类的静态变量保存,这样可以确保同一个线程的局部变量在线程的生命周期内都可以被访问,避免对象频繁创建。
4)合理的生命周期:
- 确保在线程使用
ThreadLocal完成后及时释放其关联的对象,避免由于线程未结束导致的资源浪费,尤其在线程池或长时间运行的服务中,建议在任务执行结束时清理ThreadLocal变量。
扩展知识
合理初始化 ThreadLocal
使用 ThreadLocal 时,可以通过重写 initialValue() 方法来提供默认值,这样可以避免在第一次调用 get() 方法时出现空指针异常。
例如,下面的例子为每个线程提供一个独立的 SimpleDateFormat 对象:
1 | private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")); |
适配线程池中的使用
在使用 ThreadLocal 时,要特别注意线程池的使用。线程池中的线程会被重用,因此上一个任务可能会将其 ThreadLocal 的值残留给下一个任务。为此,使用线程池时,应该确保在任务执行结束时,手动调用 remove() 清理 ThreadLocal 中的内容。
隐式线程池
我们常见的项目都是 web 项目,都会使用 Tomcat,此时需要注意隐式线程池的情况,
因为用了 tomcat ,其实处理用户请求的线程是 tomcat 线程池中的线程,这就是隐式使用。
这里有一个问题,关于 withInitial 也就是初始化值的方法。
由于 tomcat 这种隐式线程池的存在,即线程第一次调用执行 Threadlocal 之后,如果没有显示调用 remove 方法,则这个 Entry 还是存在的,那么下次这个线程再执行任务的时候,不会再调用 withInitial 方法,也就是说会拿到上一次执行的值。
但是你以为执行任务的是新线程,会初始化值,然而它是线程池里面的老线程,这就和预期不一致了,所以这里需要注意。
remove 示例代码
1 | public class ThreadLocalExample { |
Comments