Java HttpSession 内的同步是否可行?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9802165/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-16 07:48:07  来源:igfitidea点击:

Is synchronization within an HttpSession feasible?

javamultithreadingsessionservletssynchronization

提问by basZero

UPDATE:Solution right after question.

更新:问题后立即解决。

Question:

题:

Usually, synchronization is serializing parallel requests within a JVM, e.g.

通常,同步是在 JVM 中序列化并行请求,例如

private static final Object LOCK = new Object();

public void doSomething() {
  ...
  synchronized(LOCK) {
    ...
  }
  ...
}

When looking at web applications, some synchronization on "JVM global" scope is maybe becoming a performance bottleneck and synchronization only within the scope of the user's HttpSessionwould make more sense.

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

Is the following code a possibility? I doubt that synchronizing on the session object is a good idea but it would be interesting to hear your thoughts.

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

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

Key Question:
Is it guaranteed that the session object is the same instancefor all threads processing requests from the same user?

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

Summarized answer / solution:

总结答案/解决方案:

It seems that the session object itself is not always the same as it depends on the implementation of the servlet container (Tomcat, Glassfish, ...) and the getSession()method might return just a wrapper instance.

似乎会话对象本身并不总是相同,因为它取决于 servlet 容器(Tomcat、Glassfish 等)的实现,并且该getSession()方法可能只返回一个包装器实例。

So it is recommended to use a custom variable stored in the session to be used as locking object.

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

Here is my code proposal, feedback is welcome:

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

somewhere in a Helper Class, e.g. MyHelper:

在 Helper 类中的某个地方,例如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;
}

and then you can use it:

然后你可以使用它:

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

Any comments on this solution?

对此解决方案有何评论?

采纳答案by Tomasz Nurkiewicz

I found this nice explanation in spring-mvcJavaDoc for WebUtils.getSessionMutex():

我在spring-mvcJavaDoc 中找到了这个很好的解释WebUtils.getSessionMutex()

In many cases, the HttpSession reference itself is a safe mutexas well, since it will always be the same object reference for the same active logical session. However, this is not guaranteed across different servlet containers; the only 100% safe way is a session mutex.

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

This method is used as a lock when synchronizeOnSessionflag is set:

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

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

If you look at the implementation of getSessionMutex(), it actually uses some custom session attribute if present (under org.springframework.web.util.WebUtils.MUTEXkey) or HttpSessioninstance if not:

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

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

Back to plain servlet spec - to be 100% sure use custom session attribute rather than HttpSessionobject itself.

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

See also

也可以看看

回答by CodeChimp

Synchronization occurs when a lock is placed on an object reference, so that threads that reference the same object will treat any synchronization on that shared object as a toll gate.

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

So, what your question raises an interesting point: Does the HttpSession object in two separate web calls from the same session end up as the same object reference in the web container, or are they two objects that just happen to have similar data in them? I found thisinteresting discussion on stateful web apps which discusses HttpSession somewhat. Also, there is this discussionat CodeRanch about thread safety in HttpSession.

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

From those discussions, it seems like the HttpSession is indeed the same object. One easy test would be to write a simple servlet, look at the HttpServletRequest.getSession(), and see if it references the same session object on multiple calls. If it does, then I think your theory is sound and you could use it to sync between user calls.

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

回答by William R

The answers are correct. If you want to avoid the same user executes 2 different (or the same) requests at the same time, you can synchronize on the HttpSession. The best to do this is to use a Filter.

答案是正确的。如果要避免同一个用户同时执行2个不同(或相同)的请求,可以在HttpSession上进行同步。最好的方法是使用过滤器。

Notes:

笔记:

  • if your resources (images, scripts, and any non-dynamic file) also comes through the servlet, you could create a bottleneck. Then be sure, the synchonization is only done on dynamic pages.
  • Try to avoid the getSession directly, you should better test if the session already exists because a session is not automatically created for guests (as nothing has to be stored in the session). Then, if you call getSession(), the session will be created and memory will be lost. Then use getSession(false)and try to deal with the nullresult if no session already exists (in this case, don't synchronize).
  • 如果您的资源(图像、脚本和任何非动态文件)也通过 servlet,您可能会产生瓶颈。然后可以肯定,同步仅在动态页面上完成。
  • 尽量避免直接使用 getSession,您应该更好地测试会话是否已经存在,因为不会为来宾自动创建会话(因为会话中无需存储任何内容)。然后,如果您调用getSession(),会话将被创建并且内存将丢失。如果不存在会话,则使用getSession(false)并尝试处理null结果(在这种情况下,不要同步)。

回答by Peter ?tibrany

In general, don't rely on HttpServletRequest.getSession()returning same object. It's easy for servlet filters to create a wrapper around session for whatever reason. Your code will only see this wrapper, and it will be different object on each request. Put some shared lock into the session itself. (Too bad there is no putIfAbsentthough).

一般来说,不要依赖HttpServletRequest.getSession()返回相同的对象。无论出于何种原因,servlet 过滤器都可以轻松地围绕会话创建包装器。您的代码只会看到这个包装器,并且每个请求都会是不同的对象。将一些共享锁放入会话本身。(putIfAbsent可惜没有)。

回答by Amin Mozafari

The spring framework solution as mentioned by Tomasz Nurkiewicz is accidentally correct in clustered environments only because the Servlet spec requires session consistency across multiple JVMs. Otherwise, it does not do a magic on its own for the scenarios where multiple requests are spread across different machines. See the discussion in this threadthat sheds some light on the subject.

Tomasz Nurkiewicz 提到的 spring 框架解决方案在集群环境中意外正确,只是因为 Servlet 规范要求跨多个 JVM 的会话一致性。否则,对于多个请求分布在不同机器上的场景,它本身不会发挥作用。请参阅此线程中的讨论,讨论对该主题有所了解。

回答by Lucas Batistussi

As people already said, sessions can be wrapped by the servlet containers and this generates a problem: the session hashCode() is different between requests, i.e., they are not the same instance and thus can't be synchronized! Many containers allow persist a session. In this cases, in certain time, when session was expired, it is persisted on disk. Even when session is retrieved by deserialization, it is not same object as earlier, because it don't shares same memory address like when was at memory before the serialization process. When session is loaded from disk, it is put into memory for further access, until "maxInactiveInterval" is reached (expires). Summing up: the session could be not the same between many web requests! It will be the same while is in memory. Even if you put an attribute into the session to share lock, it will not work, because it will be serialized as well in the persistence phase.

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

回答by Mladen Adamovic

Using

使用

private static final Object LOCK = new Object();

you are using the same lock for all sessions and it was the core reason for deadlock I did face. So every session in your implementation has the same race condition, which is bad.

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

It needs change.

它需要改变。

Other suggested answer:

其他建议答案:

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

seems much better.

似乎好多了。

回答by Max77

Another solution suggested in "Murach's Java Servlets and JSP (3rd Edition)" book:

“Murach's Java Servlets and JSP (3rd Edition)”一书中建议的另一种解决方案:

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

回答by daiscog

Personally, I implement session-locking with the help of an HttpSessionListener*:

就个人而言,我在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
    }
}

When I need a session mutex, I can then use:

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

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

__

__

*FWIW, I really like the solution proposed in the question itself, as it provides for named session locks so that requests for independent resources don't need to share the same session lock. But if a single session lock is what you want, then this answer might be right up your street.

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