HttpSession中的同步是否可行?

更新:解决方案后问题。

题:

通常,同步是在JVM内串行化并行请求,例如

private static final Object LOCK = new Object(); public void doSomething() { ... synchronized(LOCK) { ... } ... } 

在查看Web应用程序时,“JVM全局”范围上的某些同步可能会成为性能瓶颈,仅在用户的HttpSession范围内进行同步会更有意义。

以下代码是否可能? 我怀疑同步会话对象是一个好主意,但听到你的想法会很有趣。

 HttpSession session = getHttpServletRequest().getSession(); synchronized (session) { ... } 

关键问题:
是否保证会话对象是处理来自同一用户的请求的所有线程的相同实例

总结答案/解决方案:

看来会话对象本身并不总是相同,它依赖于servlet容器(Tomcat,Glassfish,…)的实现,而getSession()方法可能只返回一个包装器实例。

因此,建议使用存储在会话中的自定义变量作为锁定对象。

这是我的代码提案,欢迎提供反馈:

帮手类中的某个地方,例如MyHelper

 private static final Object LOCK = new Object(); public static Object getSessionLock(HttpServletRequest request, String lockName) { if (lockName == null) lockName = "SESSION_LOCK"; Object result = request.getSession().getAttribute(lockName); if (result == null) { // only if there is no session-lock object in the session we apply the global lock synchronized (LOCK) { // as it can be that another thread has updated the session-lock object in the meantime, we have to read it again from the session and create it only if it is not there yet! result = request.getSession().getAttribute(lockName); if (result == null) { result = new Object(); request.getSession().setAttribute(lockName, result); } } } return result; } 

然后你可以使用它:

 Object sessionLock = MyHelper.getSessionLock(getRequest(), null); synchronized (sessionLock) { ... } 

对此解决方案有何评论?

我在Spring-mvc JavaDoc for WebUtils.getSessionMutex()找到了这个很好的解释:

在许多情况下, HttpSession引用本身也是一个安全的互斥锁 ,因为它始终是同一个活动逻辑会话的相同对象引用。 但是, 不能保证不同的servlet容器 ; 唯一100%安全的方式是会话互斥。

设置synchronizeOnSession标志时,此方法用作锁定:

 Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { return handleRequestInternal(request, response); } 

如果你看一下getSessionMutex()的实现,它实际上使用了一些自定义会话属性(如果存在)(在org.springframework.web.util.WebUtils.MUTEX键下)或HttpSession实例,如果不是:

 Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE); if (mutex == null) { mutex = session; } return mutex; 

回到简单的servlet规范 – 100%确定使用自定义会话属性而不是HttpSession对象本身。

也可以看看

通常,不要依赖HttpServletRequest.getSession()返回相同的对象。 servletfilter很容易因任何原因在会话中创建包装器。 您的代码只会看到此包装器,并且每个请求都将是不同的对象。 将一些共享锁放入会话本身。 (太糟糕了,虽然没有putIfAbsent )。

当锁定放在对象引用上时会发生同步,因此引用同一对象的线程会将该共享对象上的任何同步视为收费站。

那么,你的问题提出了一个有趣的观点:来自同一会话的两个单独的Web调用中的HttpSession对象最终是否与Web容器中的相同对象引用相同,或者它们是恰好在其中具有相似数据的两个对象? 我发现有趣的讨论有状态的网络应用程序,有些讨论HttpSession。 此外,CodeRanch还讨论了HttpSession中的线程安全问题。

从这些讨论来看,似乎HttpSession确实是同一个对象。 一个简单的测试是编写一个简单的servlet,查看HttpServletRequest.getSession(),看看它是否在多个调用中引用相同的会话对象。 如果确实如此,那么我认为您的理论是合理的,您可以使用它在用户呼叫之间进行同步。

正如人们已经说过的,会话可以由servlet容器包装,这会产生一个问题:会话hashCode()在请求之间是不同的,即它们不是同一个实例,因此无法同步! 许多容器允许持续会话。 在这种情况下,在某段时间内,当会话过期时,它会持久保存在磁盘上。 即使通过反序列化检索会话,它也不是与之前相同的对象,因为它不像在序列化过程之前的内存那样共享相同的内存地址。 从磁盘加载会话时,会将其放入内存以供进一步访问,直到达到“maxInactiveInterval”(到期)。 总结:许多Web请求之间的会话可能不一样! 在内存中它将是相同的。 即使您将一个属性放入会话以共享锁定,它也无法工作,因为它将在持久性阶段进行序列化。

答案是对的。 如果您想避免同一个用户同时执行2个不同(或相同)的请求,您可以在HttpSession上进行同步。 最好这样做是使用filter。

笔记:

  • 如果您的资源(图像,脚本和任何非动态文件)也通过servlet,您可能会产生瓶颈。 然后确保,同步只在动态页面上完成。
  • 尝试直接避免getSession,您应该更好地测试会话是否已经存在,因为没有为guest虚拟机自动创建会话(因为没有必要存储在会话中)。 然后,如果调用getSession() ,将创建会话并且内存将丢失。 然后使用getSession(false)并尝试在没有会话存在的情况下处理null结果(在这种情况下,不要同步)。

“Murach的Java Servlets和JSP(第3版)”一书中建议的另一个解决方案:

 Cart cart; final Object lock = request.getSession().getId().intern(); synchronized (lock) { cart = (Cart) session.getAttribute("cart"); } 

就个人而言,我在HttpSessionListener *的帮助下实现了会话锁定:

 package com.example; @WebListener public final class SessionMutex implements HttpSessionListener { /** * HttpSession attribute name for the session mutex object. The target for * this attribute in an HttpSession should never be altered after creation! */ private static final String SESSION_MUTEX = "com.example.SessionMutex.SESSION_MUTEX"; public static Object getMutex(HttpSession session) { // NOTE: We cannot create the mutex object if it is absent from // the session in this method without locking on a global // constant, as two concurrent calls to this method may then // return two different objects! // // To avoid having to lock on a global even just once, the mutex // object is instead created when the session is created in the // sessionCreated method, below. Object mutex = session.getAttribute(SESSION_MUTEX); // A paranoia check here to ensure we never return a non-null // value. Theoretically, SESSION_MUTEX should always be set, // but some evil external code might unset it: if (mutex == null) { // sync on a constant to protect against concurrent calls to // this method synchronized (SESSION_MUTEX) { // mutex might have since been set in another thread // whilst this one was waiting for sync on SESSION_MUTEX // so double-check it is still null: mutex = session.getAttribute(SESSION_MUTEX); if (mutex == null) { mutex = new Object(); session.setAttribute(SESSION_MUTEX, mutex); } } } return mutex; } @Override public void sessionCreated(HttpSessionEvent hse) { hse.getSession().setAttribute(SESSION_MUTEX, new Object()); } @Override public void sessionDestroyed(HttpSessionEvent hse) { // no-op } } 

当我需要会话互斥时,我可以使用:

 synchronized (SessionMutex.getMutex(request.getSession())) { // ... } 

__

* FWIW,我非常喜欢问题本身提出的解决方案,因为它提供了命名会话锁,因此对独立资源的请求不需要共享相同的会话锁。 但如果您想要一个会话锁定,那么这个答案可能就在您的街道上。

Tomasz Nurkiewicz提到的Spring框架解决方案在集群环境中意外正确,因为Servlet规范要求跨多个JVM的会话一致性。 否则,对于多个请求分布在不同计算机上的情况,它本身并不具有魔力。 请参阅本主题中有关该主题的讨论。

运用

 private static final Object LOCK = new Object(); 

你正在为所有会话使用相同的锁,这是我遇到的死锁的核心原因。 因此,您实现中的每个会话都具有相同的竞争条件,这很糟糕。

它需要改变。

其他建议的答案:

 Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE); if (mutex == null) { mutex = session; } return mutex; 

似乎好多了。