sesiones servlet manejo httpservletrequest examples controlar atributos java multithreading session servlets synchronization

java - servlet - ¿Es factible la sincronización dentro de una HttpSession?



manejo de sesiones en jsp servlets (9)

Como ya se dijo, las sesiones pueden ser envueltas por los contenedores de servlet y esto genera un problema: la sesión hashCode () es diferente entre las solicitudes, es decir, ¡no son la misma instancia y, por lo tanto, no se pueden sincronizar! Muchos contenedores permiten persistir en una sesión. En estos casos, en cierto tiempo, cuando la sesión expiró, persiste en el disco. Incluso cuando la sesión se recupera por deserialización, no es el mismo objeto que antes, porque no comparte la misma dirección de memoria como cuando estaba en la memoria antes del proceso de serialización. Cuando la sesión se carga desde el disco, se coloca en la memoria para un acceso posterior, hasta que se alcanza ("maxInactiveInterval") (caduca). En resumen: ¡la sesión podría no ser la misma entre muchas solicitudes web! Será lo mismo mientras esté en la memoria. Incluso si coloca un atributo en la sesión para compartir el bloqueo, no funcionará, ya que también se serializará en la fase de persistencia.

ACTUALIZACIÓN: Solución justo después de la pregunta.

Pregunta:

Por lo general, la sincronización consiste en serializar solicitudes paralelas dentro de una JVM, por ejemplo

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

Al buscar aplicaciones web, es posible que alguna sincronización en el ámbito "JVM global" se convierta en un cuello de botella de rendimiento y la sincronización solo dentro del alcance de la HttpSession del usuario tendría más sentido.

¿Es el siguiente código una posibilidad? Dudo que la sincronización en el objeto de la sesión sea una buena idea, pero sería interesante escuchar tus pensamientos.

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

Pregunta clave:
¿Se garantiza que el objeto de sesión es la misma instancia para todas las secuencias de procesamiento de solicitudes del mismo usuario?

Resúmen respuesta / solución:

Parece que el objeto de sesión en sí mismo no siempre es el mismo ya que depende de la implementación del contenedor de servlet (Tomcat, Glassfish, ...) y el método getSession() puede devolver solo una instancia de contenedor.

Por lo tanto, se recomienda utilizar una variable personalizada almacenada en la sesión para usarla como objeto de bloqueo.

Aquí está mi propuesta de código, los comentarios son bienvenidos:

en algún lugar de una clase de ayuda, p. ej. 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; }

y luego puedes usarlo:

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

¿Algún comentario sobre esta solución?


En general, no confíe en que HttpServletRequest.getSession() devuelva el mismo objeto. Es fácil para los filtros de servlets crear una envoltura alrededor de la sesión por cualquier razón. Su código solo verá este contenedor, y será un objeto diferente en cada solicitud. Ponga un bloqueo compartido en la sesión en sí. (Lástima que no hay putIfAbsent sin embargo).


Encontré esta buena explicación en spring-mvc WebUtils.getSessionMutex() para WebUtils.getSessionMutex() :

En muchos casos, la referencia de HttpSession también es un mutex seguro , ya que siempre será la misma referencia de objeto para la misma sesión lógica activa. Sin embargo, esto no está garantizado en diferentes contenedores de servlets ; la única forma 100% segura es un mutex de sesión.

Este método se usa como un candado cuando se establece el indicador synchronizeOnSession :

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

Si nos fijamos en la implementación de getSessionMutex() , en realidad usa algún atributo de sesión personalizado si está presente (bajo la clave org.springframework.web.util.WebUtils.MUTEX ) o instancia de HttpSession si no es así:

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

Vuelva a la especificación de servlet simple: para estar 100% seguro, use el atributo de sesión personalizado en lugar del HttpSession objeto HttpSession .

Ver también


La sincronización se produce cuando se coloca un bloqueo en una referencia de objeto, de modo que los hilos que hacen referencia al mismo objeto tratarán cualquier sincronización en ese objeto compartido como una puerta de peaje.

Entonces, ¿cuál es su pregunta que plantea un punto interesante: el objeto HttpSession en dos llamadas web separadas de la misma sesión termina como la misma referencia de objeto en el contenedor web, o son dos objetos que simplemente tienen datos similares en ellos? Encontré this discusión interesante sobre aplicaciones web con estado que discute un poco sobre HttpSession. Además, hay una discusión en CodeRanch sobre seguridad de hilos en HttpSession.

De esas discusiones, parece que HttpSession es el mismo objeto. Una prueba fácil sería escribir un servlet simple, ver HttpServletRequest.getSession () y ver si hace referencia al mismo objeto de sesión en varias llamadas. Si lo hace, entonces creo que su teoría es sólida y podría usarla para sincronizar las llamadas de los usuarios.


La solución de marco de resorte mencionada por Tomasz Nurkiewicz es accidentalmente correcta en entornos agrupados solo porque la especificación Servlet requiere consistencia de sesión en varias JVM. De lo contrario, no hace una magia en sí misma para los escenarios donde las solicitudes múltiples se extienden a través de diferentes máquinas. Vea la discusión en este hilo que arroja algo de luz sobre el tema.


Las respuestas son correctas. Si desea evitar que el mismo usuario ejecute 2 solicitudes diferentes (o las mismas) al mismo tiempo, puede sincronizar en HttpSession. Lo mejor para hacer esto es usar un filtro.

Notas:

  • si sus recursos (imágenes, secuencias de comandos y cualquier archivo no dinámico) también provienen del servlet, podría crear un cuello de botella. Luego, asegúrese de que la sincronización solo se realice en páginas dinámicas.
  • Intente evitar el getSession directamente, debería probar si la sesión ya existe porque una sesión no se crea automáticamente para los invitados (ya que no se debe almacenar nada en la sesión). Luego, si llama a getSession() , se creará la sesión y se perderá la memoria. Luego use getSession(false) y trate de lidiar con el resultado null si ya no existe sesión (en este caso, no sincronizar).

Otra solución sugerida en el libro "Java Servlets and JSP (3rd Edition)" de Murach:

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


Personalmente, implemente el bloqueo de sesiones con la ayuda de un 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 } }

Cuando necesito un mutex de sesión, puedo usar:

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

__

* FWIW, realmente me gusta la solución propuesta en la pregunta en sí, ya que proporciona bloqueos de sesiones con nombre para que las solicitudes de recursos independientes no necesiten compartir el mismo bloqueo de sesión. Pero si un bloqueo de sesión único es lo que desea, entonces esta respuesta podría ser correcta en su calle.


Utilizando

private static final Object LOCK = new Object();

está utilizando el mismo candado para todas las sesiones y fue la razón principal del punto muerto que tuve que enfrentar. Entonces, cada sesión en su implementación tiene la misma condición de carrera, lo cual es malo.

Necesita cambio.

Otra respuesta sugerida:

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

parece mucho mejor.