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 或常规参数传递。
我主要需要将User
和EntityManager
对象从用户和数据库过滤器传输到Servlet。我还发现,在后面的代码中经常和意外地需要这些,我很想在 Servlet 之外使用它们(即在 doGet 调用的嵌套代码中)。我觉得可能有更好的方法来编写更深入的代码 - 建议?但我不觉得过滤器到 Servlet 有更好的方法 - 建议?我没有 JSP/JSF/other-framework 依赖项,我使用的是带有 Servlet 和过滤器的纯 Java。我控制所有代码。谢谢!
EntityManager
是非常通常通过 DI 获得的 - 你应该认真考虑这种方法。
【参考方案1】:
ThreadLocal 比 HttpServletRequest.setAttribute(“key”, “value”) 更可取吗?
取决于具体的功能要求。
例如,JSF 将FacesContext
存储在ThreadLocal
中。这使您能够访问所有 JSF 工件,包括由 FacesServlet
执行的代码中的任何位置的“原始”HttpServletRequest
和 HttpServletResponse
,例如托管 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 所以如果可能的话,解决这个问题会很好)。再次感谢! AThreadLocal
不是单例。单例是一个静态类,具有延迟加载的静态内容,在应用程序的剩余生命周期内总是返回相同的内容。
在这种情况下,您从哪里获得 ThreadLocal 将是一个单例(或其他静态)类,因为这是在过滤器和 servlet 之间共享 ThreadLocal 的机制。我建议不要在代码中更深地使用这样的单例(除非您将单例的接口表示作为参数向下传递)。此外,除非您正在编写一个封装底层机制的框架,否则出于性能原因,我不会在属性上使用 ThreadLocal 方法,除非应用程序代码非常紧凑以至于这实际上很重要。
@increment1 - 这是一次学习经历。 @BalusC 是正确的,它不是单例(请参阅 javadoc,您可以创建多个实例,并且没有延迟加载)。在没有实例的意义上,它也不是静态类。 ThreadLocal 静态方法是工厂方法并创建多个实例。查看get()
的源代码:grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… 没有全局/静态数据结构;它在内部使用Thread
类的非公共threadLocals
成员。(续)
所以,图片中不可能有任何全局变量或单例。代码的任何部分都已经有一个每线程Thread
对象可用,ThreadLocal
仅使用每线程Thread
对象中的一个字段来提供ThreadLocal
s。因此,对单例或全局的批评都不适用。如果我错了,请告诉我,如果没有,我相信你会认为这是最好的答案。再次感谢您自己的回答。它确实帮助我更深入地挖掘并更好地理解事物。【参考方案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
更适合doGet
或doPost
的主体,但我不明白为什么使用setAttribute
/@ 将对象从Filter
传输到doGet
更好987654327@?我觉得如果发送者和接收者完全是我自己的代码,没有依赖关系,那么我永远不应该使用setAttribute
,即使代码不在处理 HttpServletRequest 的“后面”。我错过了什么吗?
如果是你自己的代码,它可能并不重要。但是,您会发现很难与其他库和代码集成。
你说它“无关紧要”。我认为 ThreadLocal 更好:“使用 ThreadLocal 的好处:类型安全和更好的性能,因为没有使用字符串键或映射”?我绝对不需要整合它,或者如果我愿意,它已经足够包含在那个时候改变它。
嗯,类型安全性很好。性能,好吧,过早的优化是万恶之源。失去未来轻松互操作的能力,在我看来不值得额外的麻烦。以上是关于ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?的主要内容,如果未能解决你的问题,请参考以下文章
ThreadStatic 与 ThreadStatic 对比ThreadLocal<T>:泛型比属性好吗?
ThreadLocal 比 HttpServletRequest.setAttribute("key", "value") 更可取吗?