HttpSession中的同步是否可行?

bas*_*ero 30 java session multithreading synchronization servlets

更新:解决方案后问题.

题:

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

private static final Object LOCK = new Object();

public void doSomething() {
  ...
  synchronized(LOCK) {
    ...
  }
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

HttpSession session = getHttpServletRequest().getSession();
synchronized (session) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

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

总结答案/解决方案:

看来会话对象本身并不总是相同,它依赖于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;
}
Run Code Online (Sandbox Code Playgroud)

然后你可以使用它:

Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

对此解决方案有何评论?

Tom*_*icz 30

我在 JavaDoc中找到了这个很好的解释WebUtils.getSessionMutex():

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

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

Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
    return handleRequestInternal(request, response);
}
Run Code Online (Sandbox Code Playgroud)

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

Object mutex = session.getAttribute(SESSION_MUTEX_ATTRIBUTE);
if (mutex == null) {
    mutex = session;
}
return mutex;
Run Code Online (Sandbox Code Playgroud)

回到普通的servlet规范 - 100%确定使用自定义会话属性而不是HttpSession对象本身.

也可以看看

  • 这忽略了一个重要时刻:线程安全地创建互斥体本身。最好的方法可能是在sessionSessiond事件中的HttpSessionListener中做到这一点。 (2认同)

Pet*_*aný 8

一般来说,不要依赖于HttpServletRequest.getSession()返回相同的对象.无论出于何种原因,servlet过滤器都很容易在会话中创建包装器.您的代码只会看到此包装器,并且每个请求都将是不同的对象.将一些共享锁放入会话本身.(太糟糕了,但没有putIfAbsent).

  • @basZero:是的,你的代码几乎与我的代码相同.我已经在会话本身上完成了同步块,但是你的解决方案更好(完全出于我的回答中所述的原因). (2认同)

bas*_*ero 2

这是我自己的解决方案:

会话对象本身似乎并不总是相同,因为它取决于 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;
}
Run Code Online (Sandbox Code Playgroud)

然后你就可以使用它:

Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

对此解决方案有何评论?

  • 在我看来,这与[双重锁定](https://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html)有类似的问题:检查对对象的引用是否为“null” ` 在同步块之外,因此您可能会得到 `false` (即非空),但由于竞争条件和编译器/VM 的写入重新排序,该对象可能尚未完全初始化。 (2认同)