Servlets em Java: instâncias, sessões e multithreading — análise completa

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Por que os servlets ainda assustam e como lidar com isso?

\n\n

Se você já visitou o Stack Overflow em busca de uma resposta para a pergunta "Como funcionam os servlets?", certamente encontrou o lendário tópico com milhares de visualizações. A pergunta que é feita repetidamente é: "Quantas instâncias de servlet são criadas? Como compartilhar dados entre requisições? E por que minha variável se comporta como se estivesse louca?"

\n\n

Vamos ser honestos: servlets não são rocket science, mas há muitas armadilhas. Especialmente quando se trata de multithreading e variáveis compartilhadas. Neste artigo, não vamos apenas recontar a documentação. Vamos pegar um exemplo real da vida, analisá-lo detalhadamente e mostrar como evitar erros comuns. Vamos lá!

\n\n

Como o servlet entra na memória: instanciação e ciclo de vida

\n\n

Vamos começar pelo simples: quantas instâncias de servlet são criadas? Resposta: uma. Sim, você ouviu direito. O contêiner de servlets (Tomcat, Jetty e outros) cria exatamente uma instância de cada servlet para toda a aplicação. E essa instância vive até que a aplicação seja descarregada.

\n\n

Veja como é o ciclo de vida típico:

\n\n
    \n
  • init() — chamado uma vez quando o servlet é carregado. Aqui você inicializa conexões com o banco de dados, configurações e outros.
  • \n
  • service() — chamado para cada requisição recebida. É aqui que o processamento ocorre.
  • \n
  • destroy() — chamado antes do descarregamento do servlet. Você libera recursos.
  • \n
\n\n

Ponto-chave: service() (e seus derivados doGet, doPost) é executado em uma thread separada para cada requisição. Ou seja, a mesma instância do servlet processa centenas de requisições simultaneamente. E é aqui que a coisa fica interessante.

\n\n

Exemplo: um servlet, muitas threads

\n\n
@WebServlet("/counter")\npublic class CounterServlet extends HttpServlet {\n    private int counter = 0;  // shared variable!\n\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) \n            throws ServletException, IOException {\n        counter++;\n        resp.getWriter().write("Counter: " + counter);\n    }\n}
\n\n

Se você executar este código e enviar 10 requisições paralelas, não obterá os números de 1 a 10 em ordem. Em vez disso, verá algo como: 2, 5, 3, 8, 1... Ou o counter pode até pular valores. Por quê? Porque counter++ não é uma operação atômica. Duas threads podem ler o mesmo valor, incrementá-lo e escrevê-lo de volta, perdendo um incremento.

\n\n

Conclusão: os campos da instância do servlet são variáveis compartilhadas. Eles são visíveis para todas as threads. E se você não sincronizar o acesso a eles, terá uma race condition.

\n\n

Sessões: como não confundir usuários

\n\n

Agora imagine: você quer armazenar dados exclusivos para cada usuário (carrinho de compras, login, configurações). Para isso, no Java Servlets existe a HttpSession. Uma sessão é criada para cada novo usuário e dura até que ele feche o navegador ou o tempo limite expire.

\n\n

A sessão é memória não compartilhada. Cada usuário recebe sua própria instância de sessão. Mas dentro da sessão, você pode armazenar qualquer objeto, e eles estarão disponíveis apenas para aquele usuário.

\n\n

Exemplo: trabalhando com sessão

\n\n
@WebServlet("/session-demo")\npublic class SessionServlet extends HttpServlet {\n    @Override\n    protected void doGet(HttpServletRequest req, HttpServletResponse resp) \n            throws ServletException, IOException {\n        HttpSession session = req.getSession();\n        Integer visitCount = (Integer) session.getAttribute("visitCount");\n        if (visitCount == null) {\n            visitCount = 0;\n                

Blogs

Book Recommendations