Por que os servlets ainda assustam e como lidar com isso?
\n\nSe 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\nVamos 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\nComo o servlet entra na memória: instanciação e ciclo de vida
\n\nVamos 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\nVeja 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
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.
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\nSe 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.
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\nSessões: como não confundir usuários
\n\nAgora 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\nA 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\nExemplo: 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