Est la synchronisation au sein d'une HttpSession faisable?
Mise à JOUR: Solution de droit après la question.
Question:
D'habitude, la synchronisation est la sérialisation des demandes parallèles à l'intérieur d'une JVM, par exemple
private static final Object LOCK = new Object();
public void doSomething() {
...
synchronized(LOCK) {
...
}
...
}
Lorsque l'on regarde les applications web, une synchronisation sur "JVM" portée globale est peut-être de devenir un goulot d'étranglement des performances et de la synchronisation uniquement dans le champ d'application de l'utilisateur HttpSession aurait plus de sens.
Est le code suivant une possibilité? Je doute que la synchronisation sur l'objet de session est une bonne idée, mais il serait intéressant d'entendre vos pensées.
HttpSession session = getHttpServletRequest().getSession();
synchronized (session) {
...
}
Question Clé:
Est-il garanti que l'objet session est la même instance pour tous les threads de traitement des demandes provenant de la même utilisateur?
Résumé de réponse /solution:
Il semble que l'objet de la session elle-même n'est pas toujours le même, puisqu'il dépend de la mise en œuvre du conteneur de servlet (Tomcat, Glassfish, ...) et la getSession()
méthode peut retourner juste un wrapper instance.
Il est donc recommandé d'utiliser une variable personnalisée stockées dans la session pour être utilisé comme objet de verrouillage.
Voici mon code de la proposition, commentaires sont les bienvenus:
quelque part dans une Classe d'assistance, par exemple 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;
}
et puis vous pouvez l'utiliser:
Object sessionLock = MyHelper.getSessionLock(getRequest(), null);
synchronized (sessionLock) {
...
}
Des commentaires sur cette solution?
- double possible de Est HttpSession thread-safe, sont set/get Attribut thread-safe opérations?
- Il n'est pas un doublon comme lié question est au sujet de la sécurité des threads de variables de session. Je me demande si vous pouvez verrouiller la session de l'objet lui-même.
- Je suis en désaccord, l'OP demande quel est l'objet qui est sûr pour être utilisé pour
HttpSession
de synchronisation/verrouillage, pas de savoir siHttpSession
est thread-safe ou pas. - Dans le doute, vous pourriez mettre une propriété dans votre session, juste pour le verrouillage. Sachez cependant, que
synchronized
ne fonctionnera pas si vous utilisez un cluster avec la réplication de session et lorsque plusieurs demandes pour la même session sont traitées par des serveurs différents. - comme l'extrait de code dans ma question? J'ai mis à jour avec une solution possible.
- Oui, c'est ce que j'avais à l'esprit. Avec moins de verrouillage pour ajouter la propriété, si. 🙂 En fonction de votre framework web, vous pouvez essayer de mettre le verrou de l'objet dans le contexte de session immédiatement après la création de session.
- Oui, pensé à ça aussi, mais de cette façon, si le verrou de l'objet est disponible dans la session, le verrouillage global n'est plus utilisé, donc il ne devrait pas être que beaucoup de pertes de performances dans ce cas (mais le code est moche, je sais).
- Le VERROUILLAGE global de l'objet est nécessaire dans votre cas (sinon deux demandes de la même utilisateur avec le même session peut créer différents SESSION_LOCK objets), et de l'omi, il va créer une certaine perte de performances puisque toutes les demandes (même à partir de différents utilisateurs) sera sérialisé.
- La meilleure façon (omi), comme décrit dans la accepté de répondre, est à l'aide de la HttpSessionMutexListener en combinaison avec WebUtils.getSessionMutex(session) pour obtenir le verrou approprié. Dans ce cas, le seul problème pourrait être si deux demandes de session de l'utilisateur même essayer de créer une session (je ne vois pas de Printemps à l'aide d'un verrou global pour cela), ce qui devrait arriver avec une petite probabilité que votre cas.
- J'aime votre propsed solution; il est robuste et offre des sessions multiples verrous à des fins différentes (une très bonne idée). Mais au lieu de l'ajouter à votre question, vous devez l'ajouter comme une réponse ci-dessous dans son propre droit; de cette façon, je peut voter votre réponse ainsi que votre question. 🙂
Vous devez vous connecter pour publier un commentaire.
J'ai trouvé cette belle explication dans spring-mvc JavaDoc pour
WebUtils.getSessionMutex()
:Cette méthode est utilisée en tant que verrou lorsque
synchronizeOnSession
indicateur est défini:Si vous regardez à la mise en œuvre de
getSessionMutex()
, il utilise certains de session personnalisé attribut s'il est présent (sousorg.springframework.web.util.WebUtils.MUTEX
clé) ouHttpSession
instance si ce n':Ordinaire, servlet spec - être 100% sûr d'utiliser des attributs de session plutôt que de
HttpSession
objet lui-même.Voir aussi
HttpSessionListener
sur unsessionCreated
événement.En général, ne comptez pas sur
HttpServletRequest.getSession()
retour même objet. Il est facile de servlet filtres pour créer un wrapper autour de session pour quelque raison que ce soit. Votre code ne verrez ce wrapper, et ce sera l'objet est différent sur chaque demande. Mettre quelques verrou partagé dans la session elle-même. (Dommage qu'il n'y est pasputIfAbsent
tout de même).De synchronisation se produit lorsqu'un verrou est placé sur un objet de référence, de sorte que les fils qui font référence au même objet à traiter toute synchronisation sur cet objet partagé comme une barrière de péage.
Donc, ce que votre question soulève un point intéressant: l'objet HttpSession séparée en deux appels web à partir de la même session, fin comme de la même référence d'objet dans le conteneur web, ou sont-ils deux objets qui venez d'arriver à avoir des données similaires dans leur? J'ai trouvé cette discussion intéressante sur la dynamique des applications web qui discute HttpSession un peu. Aussi, il est cette discussion à CodeRanch sur la sécurité des threads en HttpSession.
À partir de ces discussions, il semble que le HttpSession est en effet le même objet. Un moyen simple serait d'écrire une servlet simple, regardez la HttpServletRequest.getSession(), et voir si elle fait référence à la même session sur l'objet d'appels multiples. Si c'est le cas, je pense que ta théorie est bonne, et vous pouvez l'utiliser pour la synchronisation entre l'utilisateur appels.
Que les gens déjà dit, ces sessions peuvent être enveloppé par les conteneurs de servlet et cela génère un problème: la session hashCode() est différent entre les demandes, c'est à dire, ils ne sont pas de la même instance et ne peut donc pas être synchronisé! De nombreux conteneurs permettent de persister une session. Dans ce cas, à un moment donné, lors de la session a expiré, il est conservé sur le disque. Même lorsqu'une session est récupéré par la désérialisation, il n'est pas le même objet que les précédentes, car il n'a pas d'actions même adresse mémoire comme quand a été à la mémoire, avant le processus de sérialisation. Lorsqu'une session est chargé à partir du disque, il est mis en mémoire pour d'autres accès, jusqu'à ce que "maxInactiveInterval" est atteint (expire). En résumé: la session n'a pas pu être le même entre le nombre de requêtes web! Ce sera la même chose, tout est dans la mémoire. Même si vous mettez un attribut dans la session pour le partage de verrouillage, il ne fonctionnera pas, car il sera sérialisé, ainsi dans la persistance de la phase.
Les réponses sont correctes. Si vous voulez éviter le même utilisateur exécute 2 différents (ou le même) demandes en même temps, vous pouvez synchroniser sur le HttpSession. Le mieux à faire, c'est d'utiliser un Filtre.
Notes:
getSession()
, la session sera créé et la mémoire seront perdues. Ensuite, utilisezgetSession(false)
et d'essayer de traiter avec lenull
résultat si aucune session n'existe déjà (dans ce cas, ne pas synchroniser).Une autre solution proposée dans "Murach Java Servlets et les JSP (3e Édition)" livre:
Personnellement, je mettre en œuvre à la session de verrouillage à l'aide d'un HttpSessionListener*:
Quand j'ai besoin d'une séance de mutex, je peux utiliser:
__
*FWIW, j'aime vraiment la solution proposée dans la question elle-même, car il fournit pour la session nommée serrures ainsi que les demandes de ressources indépendantes n'ont pas besoin de partager la même session de verrouillage. Mais si une seule session de verrouillage est ce que vous voulez, alors cette réponse peut-être raison de votre rue.
Le framework spring solution, comme mentionné par Tomasz Nurkiewicz est accidentellement correcte dans des environnements en cluster seulement parce que la Servlet spec nécessite session de la cohérence entre plusieurs machines virtuelles. Sinon, ce n'est pas faire de la magie sur sa propre pour les cas où plusieurs requêtes sont réparties à travers les différentes machines. Voir la discussion dans ce fil qui jette quelque lumière sur le sujet.
À l'aide de
vous utilisez la même serrure pour toutes les séances et c'était la raison essentielle de l'impasse j'ai fait face.
Donc, à chaque session de la mise en œuvre a la même condition de course, ce qui est mauvais.
Il a besoin de changement.
D'autres propositions de réponse:
semble beaucoup mieux.
LOCK
est utilisé uniquement lors de la session attribué est créé, il est nécessaire de synchroniser l'échelle mondiale en vue d'éviter les demandes de création d'objets à la session.