Usando request.getSession () como um objecto de bloqueio?
-
10-07-2019 - |
Pergunta
Eu tenho algum código java que obtém e define um atributo de sessão:
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
A fim de tornar este código thread-safe, eu gostaria de envolvê-la em um bloco sincronizado. Mas o que eu uso como objeto de bloqueio? Faz sentido usar a sessão?
synchronized (session) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}
Solução
É geralmente desaprovado usar um bloqueio que você não tem nenhum controle sobre. Um bloqueio deve ser com escopo tão firmemente quanto possível e desde que a sessão é mais ou menos um objeto global, ele não se encaixa o projeto de lei. Tente usar um bloqueio separado do java.util.concurrent.locks pacote eo alcance que a sua classe.
Outras dicas
No contexto de servlets? Servlets podem ser distribuídos através de múltiplos processos, portanto, você não pode ter sempre o mesmo objeto sessão. Um corolário disso é que um servlet container pode decidir dar-lhe um objeto de sessão diferente no mesmo processo.
IIRC, Brian Goetz escreveu um artigo interessante sobre a dificuldade de fazer as coisas direito com sessões.
Meu conselho:. Mantenha-se afastado de sessões, tanto quanto possível, e não bloquear objetos aleatórios (usar um objeto de bloqueio que não tem outro propósito)
Eu levei um olhar para o artigo que você postou. Você pode pular a sincronização de todos juntos e ter a mesma abordagem que o autor fez usando comparar-e-set para garantir que seus dados estão corretos:
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<TYPE> holder
= (AtomicReference<TYPE>) ctx.getAttribute(TEST_ATTR);
while (true) {
TYPE oldVal = holder.get();
TYPE newVal = computeNewVal(oldVal);
if (holder.compareAndSet(oldVal, newVal))
break;
}
holder.compareAndSet (antigo, novo) retornará false se algum outro segmento atualizou o valor de "titular" desde a última lê-lo. holder.compareAndSet (,) é colocado em um tempo (true) loop de modo que, se o valor fez a mudança antes de você fosse capaz de escrevê-lo, então você tem a chance de ler o valor novamente e re-tentar a sua escrita.
A especificação não garante que isso vai ajudar em tudo:
synchronized (session) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}
(Pode funcionar para implementações específicas, mas não há garantias de que ele vai trabalhar em todos os recipientes.)
Servlet 2.5 MR6 diz:
Vários servlets execução de solicitação de tópicos podem ter acesso ativo para o mesmo objeto da sessão, ao mesmo tempo. O recipiente tem de assegurar que a manipulação das estruturas de dados internas que representam a sessão de atributos é realizada de um modo multitarefa. O desenvolvedor tem a responsabilidade de threadsafe acesso aos próprios objetos de atributo. Isto irá proteger a coleção de atributos dentro do objeto HttpSession de acesso simultâneo, eliminando a oportunidade para uma aplicação a causa que a coleta para se tornar corrompido.
Basicamente, a especificação torna o seu problema. Sua solução terá de ser adaptado para o design do aplicativo e plano de implantação. Eu não tenho certeza de que existe uma solução global para o problema que irá funcionar em todos os casos; no entanto, você pode obter uma resposta melhor se você é mais específico sobre a arquitetura de sua aplicação e configuração do servidor de aplicação.
O seu código não vai funcionar por pelo menos duas razões.
1) Se a sessão não existe, então você pode facilmente criar duas vezes para o mesmo usuário, e ter uma condição de corrida feio.
2) Se a sessão não é o mesmo objeto em threads, então não vai funcionar de qualquer maneira. A sessão será provavelmente equals()
à mesma sessão em outro segmento, mas isso não vai funcionar.
Você não precisa de bloqueio desde session.setAttribute()
é o segmento de seguros (ver comentário especificação servlet de @McDowell acima).
No entanto, vamos usar um exemplo diferente. Vamos dizer que você queria chek o valor do atributo, em seguida, atualizá-lo se <= 100. Neste caso você precisará sincronizar o bloco de código para o getAttribute()
a comparar <= 100 eo setAttribute()
.
Agora, o que você deve usar para o bloqueio? Lembre-se que não há sincronização se diferentes objetos são usados ??para o bloqueio. Assim blocos de código diferentes devem usar o mesmo objeto. Sua escolha do objeto session
pode ser apenas multa. Lembre-se também que blocos de código diferentes podem acessar a sessão (ambos de leitura / gravação), mesmo se tiver tomado um bloqueio, a menos que outro código também bloqueia no objeto sessão. Uma armadilha aqui é que muitos lugares em seu código tomar um bloqueio no objeto de sessão e, portanto, tem que esperar. Por exemplo, se o seu atributo sessão bloco utiliza o código de um e outro pedaço de código usa sessão atributo B, seria bom se eles não precisam esperar uns sobre os outros por tanto tomar um bloqueio no objeto de sessão. O uso de estática objetos nomeados LockForA e LockForB pode ser melhores opções para o seu código para uso - por exemplo, synchronized (LockForA) { }.
Eu tive o mesmo problema e não queria âmbito que a minha classe porque a criação de meu objeto pode ter um segundo e tenda tópicos de outras sessões onde o objeto foi já criados. Eu não quero usar o objeto de sessão do pedido para sincronizar em causa a implementação do servidor de aplicação pode retornar uma fachada de sessão differen em diferentes solicitações e sincronização em diferentes objetos simplesmente não funciona. Então eu optar por colocar um objeto na sessão para o uso como um bloqueio e usar a linguagem dupla verificação para garantir o bloqueio é criado apenas uma vez. Sincronizar no âmbito da MyObject não é mais um problema porque a criação de um objeto é bastante rápido.
Object lock = session.getAttribute("SessionLock");
if (lock == null) {
synchronized (MyObject.class) {
lock = session.getAttribute("SessionLock");
if(lock == null) {
lock = new Object();
session.setAttribute("SessionLock", lock);
}
}
}
synchronized (lock) {
Object obj = session.getAttribute(TEST_ATTR);
if (obj==null) {
obj = new MyObject();
session.setAttribute(obj);
}
}