Spring 请求范围与 java 线程本地

Posted

技术标签:

【中文标题】Spring 请求范围与 java 线程本地【英文标题】:Spring Request scope vs java thread-local 【发布时间】:2014-10-13 21:30:09 【问题描述】:

大量(每秒约 50,000 个请求)java web-app 我正在使用 ThreadLocal 来执行一个任务,该任务应该在每个请求范围内执行。

我可以使用 Spring 请求范围实现相同的效果,但我想知道哪个性能更好?

在代码中,使用 ThreadLocal:

private static final ThreadLocal<SomeClass> myThreadLocal = new ThreadLocal<SomeClass>();

对于每个http请求设置:

myThreadLocal.set(new SomeClass());

使用 Spring 请求范围:

@Component
@Scope("request")
public class SomeClass
...

现在,什么会更贵:

myThreadLocal.get();

SpringContext.getBean(SomeClass.class);

不知道有没有人尝试过这样的基准测试?

【问题讨论】:

你也可以做基准测试。 但我的直觉告诉我ThreadLocal 会更快,如果实施得当的话。 基准测试给出了明确的答案,但即使您可能不知道 why .. 【参考方案1】:

如果我们考虑传统的 Java 方法,可以从下面的引用中推断出答案要慢得多:

因为反射涉及动态解析的类型, 无法执行某些 Java 虚拟机优化。 因此,反射操作的性能比它们的 非反射对应物,应在代码部分中避免 在性能敏感的应用程序中经常调用它们。

引用自 JavaDoc 关于反射 - http://java.sun.com/docs/books/tutorial/reflect/index.html

所以由于 Spring 使用 ReflectiongetBean() 方法,SpringContext.getBean(SomeClass.class); 方法应该更慢。

编辑

另请注意,ThreadLocal 还具有嵌入的缓存,因此只要您在这些线程中重复使用信息,它肯定会更快。

【讨论】:

【参考方案2】:

关于ThreadLocal 解决方案,我想补充一点,您的 Web 服务器中可能有一个线程池(例如:Tomcat),并且您的线程局部变量在每个请求完成后实际上不会被清除为处理线程不要在启用线程池的情况下死掉。

您需要在完成每个请求后手动清除线程局部变量 (threadLocal.remove())。为此,您可以使用某些 Spring 请求/响应拦截器中的某种 afterCompletion()

【讨论】:

你能更准确一点吗?我的意思是我的问题:***.com/questions/43181576/…。特别是,如果我们可以假设 afterCompletion 将由执行 preHandle 并执行控制器主体的同一线程执行,调用 dao ? 如果你有一个过滤器(例如,@WebFilter)并且你在过滤器的预处理中设置了你的线程本地值,那么你不需要担心之后清除线程本地值. 删除线程局部变量太重要了!!如果没有完成,您可能会看到非常奇怪的结果。 在每次这些请求之后调用 remove 的性能成本如何? 我会在开始时清理 threadLocal 而不是使用 afterCompletion()。在大多数情况下,你的逻辑的执行都是从同一个点开始的,如果出现异常,它可以在不同的地方停止。【参考方案3】:

Spring 解决方案将花费更多,但会使 IMO 代码更简洁。获取、创建、初始化和存储 bean 涉及很多步骤。但是,您不必像 ThreadLocal 那样考虑清除请求范围的 bean。清理对应的ServletRequest时会被回收。

【讨论】:

以上是关于Spring 请求范围与 java 线程本地的主要内容,如果未能解决你的问题,请参考以下文章

Spring Cloud Hystrix 请求熔断与服务降级

这12道Spring面试题要是还不会的话?就白干了!

Spring中bean的作用范围

Spring Bean单例与线程安全

推荐书单

Elasticsearch线程池配置