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);
  }
}
Foi útil?

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.

http: // java .sun.com / JavaSE / 6 / docs / api / java / util / concorrente / atômica / AtomicReference.html

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);
  }
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top