ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?

Posted

技术标签:

【中文标题】ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?【英文标题】:Is ThreadLocal preferable to HttpServletRequest.setAttribute("key", "value")? 【发布时间】:2012-04-23 04:47:51 【问题描述】:

servlet 规范(请参阅我之前的问题)保证同一个线程将执行所有过滤器和关联的 Servlet。鉴于此,如果有使用 ThreadLocal 的选项(假设您正确清理),我认为使用 HttpServletRequest.setAttribute 传递数据没有任何用处。我觉得使用ThreadLocal 有两个好处:类型安全和更好的性能,因为没有使用字符串键或映射(可能通过(非字符串)线程 id 进入线程集合除外)。

有人可以确认我是否正确,以便我可以继续放弃setAttribute吗?

【问题讨论】:

请求属性首先是用于 JSP 渲染。 我没有使用 JSP(“如果可以选择使用 ThreadLocal”),所以这个问题是关于出于其他原因使用它们。 那么您希望在哪里/如何使用这些值? 听起来你只是在使用全局变量而不是 DI 或常规参数传递。 我主要需要将UserEntityManager 对象从用户和数据库过滤器传输到Servlet。我还发现,在后面的代码中经常和意外地需要这些,我很想在 Servlet 之外使用它们(即在 doGet 调用的嵌套代码中)。我觉得可能有更好的方法来编写更深入的代码 - 建议?但我不觉得过滤器到 Servlet 有更好的方法 - 建议?我没有 JSP/JSF/other-framework 依赖项,我使用的是带有 Servlet 和过滤器的纯 Java。我控制所有代码。谢谢! EntityManager非常通常通过 DI 获得的 - 你应该认真考虑这种方法。 【参考方案1】:

ThreadLocal 比 HttpServletRequest.setAttribute(“key”, “value”) 更可取吗?

取决于具体的功能要求。

例如,JSF 将FacesContext 存储在ThreadLocal 中。这使您能够访问所有 J​​SF 工件,包括由 FacesServlet 执行的代码中的任何位置的“原始”HttpServletRequestHttpServletResponse,例如托管 bean。大多数其他基于 Java 的 MVC 框架都遵循相同的示例。

根据您的评论,

我主要需要将 User 和 EntityManager 对象从用户和数据库过滤器传输到 Servlet。我还发现,在后面的代码中经常和意外地需要这些,我很想在 Servlet 之外使用它们(即在 doGet 调用的嵌套代码中)。我觉得可能有更好的方法来编写更深入的代码 - 建议?

至于我假设这是一个会话属性的User 示例,我宁愿遵循与JSF 相同的方法。创建一个ThreadLocal<Context>,其中Context 是您的自定义包装类,其中包含对当前HttpServletRequest 的引用,也可能还有HttpServletResponse,以便您可以在代码中的任何位置访问它们。如有必要,提供方便的方法以直接从Context 类中获取User

至于EntityManager 示例,您可以采用相同的方法,但我个人不会将其放在相同 ThreadLocal<Context> 中,而是使用不同的方法。或者,更好的是,只需从服务层的 JNDI 获取它,这将允许您对事务进行更细粒度的控制。在任何情况下,请绝对确保您正确处理提交/关闭。从容器中接管持久性和事务管理应该非常小心。我真的会重新考虑反对使用现有且设计良好的 API/框架(如 EJB/JPA),否则您将冒着完全浪费时间重新发明所有已经标准化的 API 和东西的风险。

另见:

Retrieving Web Session from a POJO Outside the Web Container Design Patterns web based applications

【讨论】:

感谢您的回答。我更喜欢您的方法,但为了完整起见,您是否愿意就 ThreadLocal 的“单身人士不好”批评发表意见? (所有其他答案都提供了这种批评,并且更喜欢 setAttribute 所以如果可能的话,解决这个问题会很好)。再次感谢! A ThreadLocal 不是单例。单例是一个静态类,具有延迟加载的静态内容,在应用程序的剩余生命周期内总是返回相同的内容。 在这种情况下,您从哪里获得 ThreadLocal 将是一个单例(或其他静态)类,因为这是在过滤器和 servlet 之间共享 ThreadLocal 的机制。我建议不要在代码中更深地使用这样的单例(除非您将单例的接口表示作为参数向下传递)。此外,除非您正在编写一个封装底层机制的框架,否则出于性能原因,我不会在属性上使用 ThreadLocal 方法,除非应用程序代码非常紧凑以至于这实际上很重要。 @increment1 - 这是一次学习经历。 @BalusC 是正确的,它不是单例(请参阅 javadoc,您可以创建多个实例,并且没有延迟加载)。在没有实例的意义上,它也不是静态类。 ThreadLocal 静态方法是工厂方法并创建多个实例。查看get()的源代码:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 没有全局/静态数据结构;它在内部使用Thread 类的非公共threadLocals 成员。(续) 所以,图片中不可能有任何全局变量或单例。代码的任何部分都已经有一个每线程Thread 对象可用,ThreadLocal 仅使用每线程Thread 对象中的一个字段来提供ThreadLocals。因此,对单例或全局的批评都不适用。如果我错了,请告诉我,如果没有,我相信你会认为这是最好的答案。再次感谢您自己的回答。它确实帮助我更深入地挖掘并更好地理解事物。【参考方案2】:

我建议不要使用 ThreadLocal。

我明白你为什么要考虑它,但我认为你陷入了过早的优化陷阱。 ThreadLocals 本质上是全局变量——很少是好的设计。节省的时间将是微不足道的。我保证这永远不会成为您服务器吞吐量的瓶颈。

如果您想开始对某些请求使用异步响应(例如支持长轮询),使用 ThreadLocal 也可能会给您带来问题,因为线程模型完全不同。

【讨论】:

类型安全是我更重要的关注点,而且从字符串键映射到直接访问的切换也不属于过早优化的范畴。有点像从整数的字符串表示转移到字节表示不会是过早的优化。总之,主要的权衡是类型安全与“好的设计”(单例、全局等)。具体的用例是将数据从过滤器传输到 servlet,我无法看到具体/具体的演示为什么它不是“好的设计”。我欣赏良好的设计,但对于代码,我偏向于适合特定用例的内容。【参考方案3】:

以这种方式使用 ThreadLocal 意味着您依赖某种类型的单例来维护共享状态。最终,这通常被认为是糟糕的设计,因为它有许多缺点,包括难以封装功能、意识到依赖关系以及无法交换(或模拟)功能。

与在单例中处理不太常见的 ThreadLocal 变量提议相比,使用属性或会话的性能开销可能是值得的。但是,这当然取决于您的特定用例和项目。由于难以与 servlet 共享应用程序状态(难以注入依赖项),在 servlet 中使用单例作为维护应用程序状态的一种方式有些常见,但这些对象单例对象维护每个用户状态(在 EJB 之外)是不常见的)。

如果使用会话或将容器对象设置为属性,您将只处理通过字符串的单个提取(将是 O(1)),然后处理容器类型的单个案例(具有您想要的所有值的特定访问器)。进一步调用代码应该适当地使用参数,并尽可能避免使用任何类型的全局或单例。

最终,在考虑以性能为名的有点不寻常(尽管很聪明)的解决方案之前,请始终先测试简单的实现,看看它的性能是否足够。与花费在 db 查询和 tcp 连接建立上的 20+ms 相比,这里可能节省的 2ns 很可能微不足道。

【讨论】:

我没有看到单身人士。该对象是ThreadLocal 的每个线程,它与set/getAttribute 的每个请求具有一对一的对应关系。用例是将过滤器查找的用户对象传输到 Servlet。在过滤器中我可以说setAttribute("user", user);ThreadStore.setUser(user);在 Servlet 中,相应地:User user = (User)getAttribute("user");ThreadStore.getUser() 其中 ThreadStore 是 ThreadLocal 周围的便利层。我没有看到单身人士。 @agksmehx 我所指的单例将是您持有可全局访问的 ThreadLocal 变量的对象或静态类(它必须以某种全局静态可访问的方式存在,以便您按照指示使用它) . JVM 不会对你隐藏所有这些吗?请看docs.oracle.com/javase/7/docs/api/java/lang/ThreadLocal.html hmm... 文档中的示例确实有一个单例。我仍然不相信这是一件坏事,或者即使它是必要的。但你的回答肯定会赢得我的支持:) @agksmehx String 对象本身缓存了自己的哈希码(甚至引号中的字符串文字也有一个实际的字符串对象实例)。在您的情况下,我通常会做的是在包含所有其他对象的属性或会话中拥有一个对象,所以我只需要担心一个演员表。 (例如 UserContext context = (UserContext) request.getAttribute(USER_CONTEXT_KEY); int someInt = context.getSomeInt(); )。然后,更深入的代码将 UserContext 对象作为参数(而不是传递请求或类似的东西)。如果 UserContext 是一个接口,则加分。【参考方案4】:

如果您尝试为处理 HttpServletRequest 的代码“隐藏”设置“全局”变量,则线程本地会更好地工作。如果您正在使用 JSP/JSF 页面,或其他从 HttpServletRequest 读取的 Web UI 组件,那么您最终将不得不自己从 ThreadLocal 中提取信息。 对于大多数网络编程来说,这 2 个是不等价的。

【讨论】:

谢谢。您似乎确实暗示request.setAttribute 更适合doGetdoPost 的主体,但我不明白为什么使用setAttribute/@ 将对象从Filter 传输到doGet 更好987654327@?我觉得如果发送者和接收者完全是我自己的代码,没有依赖关系,那么我永远不应该使用setAttribute,即使代码不在处理 HttpServletRequest 的“后面”。我错过了什么吗? 如果是你自己的代码,它可能并不重要。但是,您会发现很难与其他库和代码集成。 你说它“无关紧要”。我认为 ThreadLocal 更好:“使用 ThreadLocal 的好处:类型安全和更好的性能,因为没有使用字符串键或映射”?我绝对不需要整合它,或者如果我愿意,它已经足够包含在那个时候改变它。 嗯,类型安全性很好。性能,好吧,过早的优化是万恶之源。失去未来轻松互操作的能力,在我看来不值得额外的麻烦。

以上是关于ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal 变量的性能

ThreadStatic 与 ThreadStatic 对比ThreadLocal<T>:泛型比属性好吗?

ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?

ThreadLocal 源码解析

三十八 ThreadLocal

java多线程基本概述——ThreadLocal