Servlets en Java: instancias, sesiones y concurrencia — análisis a fondo

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

¿Por qué los servlets siguen asustando y cómo vivir con ello?

\n\n

Si alguna vez has mirado en Stack Overflow buscando una respuesta a la pregunta «¿Cómo funcionan los servlets?», seguramente te topaste con el legendario hilo de miles de visitas. La pregunta que se hace una y otra vez: «¿Cuántas instancias de un servlet se crean? ¿Cómo compartir datos entre peticiones? ¿Y por qué mi variable se comporta como si estuviera loca?»

\n\n

Seamos honestos: los servlets no son ciencia espacial, pero hay suficientes trampas. Especialmente cuando se trata de concurrencia y variables compartidas. En este artículo no solo repetiremos la documentación. Tomaremos un ejemplo real de la vida, lo analizaremos en detalle y mostraremos cómo no pisar el rastrillo. ¡Allá vamos!

\n\n

Cómo llega un servlet a la memoria: instanciación y ciclo de vida

\n\n

Empecemos con lo simple: ¿cuántas instancias de un servlet se crean? Respuesta: una. Sí, no oíste mal. El contenedor de servlets (Tomcat, Jetty y otros) crea exactamente una instancia de cada servlet para toda la aplicación. Y esa instancia vive hasta que la aplicación se descarga.

\n\n

Así es como se ve un ciclo de vida típico:

\n\n
    \n
  • init() — se llama una vez al cargar el servlet. Aquí inicializas conexiones a la BD, configuraciones y demás.
  • \n
  • service() — se llama por cada petición entrante. Aquí es donde ocurre el procesamiento.
  • \n
  • destroy() — se llama antes de descargar el servlet. Limpias recursos.
  • \n
\n\n

Punto clave: service() (y sus derivados doGet, doPost) se ejecuta en un hilo separado para cada petición. Es decir, la misma instancia del servlet procesa cientos de peticiones simultáneamente. Y aquí es donde empieza lo interesante.

\n\n

Ejemplo: un servlet, muchos hilos

\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

Si ejecutas este código y envías 10 peticiones paralelas, no obtendrás los números del 1 al 10 en orden. En su lugar, verás algo como: 2, 5, 3, 8, 1... O incluso counter puede saltarse valores. ¿Por qué? Porque counter++ no es una operación atómica. Dos hilos pueden leer el mismo valor, incrementarlo y escribirlo de vuelta, perdiendo un incremento.

\n\n

Conclusión: los campos de instancia del servlet son variables compartidas. Son visibles para todos los hilos. Y si no sincronizas el acceso a ellos, obtienes una condición de carrera.

\n\n

Sesiones: cómo no confundir a los usuarios

\n\n

Ahora imagina: quieres almacenar datos únicos para cada usuario (carrito de compras, inicio de sesión, configuraciones). Para esto, en Java Servlets existe HttpSession. La sesión se crea para cada nuevo usuario y vive hasta que cierre el navegador o expire el tiempo de espera.

\n\n

La sesión no es memoria compartida. Cada usuario obtiene su propia instancia de sesión. Pero dentro de la sesión puedes almacenar cualquier objeto, y estos serán accesibles solo para ese usuario.

\n\n

Ejemplo: trabajar con sesión

\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