Vale, vamos a ampliar la parte de servlets que aparece en el documento, porque aunque en el texto original solo se menciona como ejemplo de tecnología que usa hilos, en realidad hay bastante profundidad detrás en cómo interactúan los servlets con la concurrencia en Java.
Servlets y concurrencia en Java
Relacionado: IDOR. Lectura y escritura coordinada entre procesos sin kernel. Condiciones de Carrera. 2025 09 02 Gestion por procesos y BPMN. 2025 03 06 diseno y desarrollo de sistema.
En el contexto de programación concurrente de memoria común en Java, los servlets son un ejemplo real y cotidiano de aplicaciones que se ejecutan en un entorno multihilo sin que el desarrollador tenga que gestionar explícitamente la creación o destrucción de hilos.
Un servlet es un componente Java que se ejecuta en un servidor web o de aplicaciones (como Apache Tomcat, Jetty, JBoss/WildFly o GlassFish) y que procesa peticiones HTTP provenientes de clientes (normalmente navegadores web). El contenedor de servlets gestiona todo el ciclo de vida del servlet, desde su carga hasta su destrucción.
Concurrencia en servlets
Por diseño, un contenedor de servlets:
-
Crea una única instancia de cada servlet definido.
-
Usa un hilo diferente para atender cada petición entrante al mismo servlet.
-
Reutiliza la misma instancia para múltiples hilos concurrentes.
Esto significa que todas las variables de instancia (atributos no estáticos) de un servlet son compartidas por todos los hilos que lo atienden simultáneamente.
Ejemplo simplificado:
@WebServlet("/contador")
public class ContadorServlet extends HttpServlet {
private int contador = 0; // Variable compartida
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
contador++; // Posible condición de carrera
resp.getWriter().println("Visitas: " + contador);
}
}En este código, si varios usuarios hacen peticiones casi al mismo tiempo, el incremento de contador no está protegido. Esto puede provocar resultados incorrectos debido a condiciones de carrera.
Implicaciones de la programación concurrente
-
No usar variables de instancia mutables sin protección
Si se necesita un estado compartido, debe protegerse con sincronización (synchronized),AtomicInteger,volatileo utilizando estructuras de datos concurrentes (ConcurrentHashMap, etc.). -
Preferir variables locales a variables de instancia
Las variables locales dentro dedoGet()odoPost()son seguras por naturaleza porque cada hilo obtiene su propia copia en la pila de ejecución. -
Evitar estados globales
Las variablesstatictambién son compartidas entre hilos y entre instancias de servlets, lo que incrementa el riesgo de inconsistencias. -
Sincronización explícita
En caso de tener que modificar datos compartidos:private final AtomicInteger contador = new AtomicInteger(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { int valor = contador.incrementAndGet(); resp.getWriter().println("Visitas: " + valor); }
Cómo el contenedor de servlets gestiona los hilos
-
El contenedor mantiene un pool de hilos (muy parecido a un
ThreadPoolExecutor). -
Cada nueva petición HTTP se asigna a un hilo libre del pool.
-
Si todos los hilos están ocupados, las nuevas peticiones esperan en una cola.
-
El programador no ve directamente los hilos, pero su código se ejecuta en ellos.
Esto hace que los servlets sean un caso práctico de programación concurrente de memoria común en Java, donde los datos compartidos residen en el heap de la JVM y son accesibles por múltiples hilos simultáneamente.
Relación con Thread-safety
En el documento original, los servlets se citan como ejemplo de tecnología que crea hilos automáticamente en nombre del programador.
Esto implica que aunque el desarrollador no haya escrito new Thread(), su código ya está expuesto a un contexto multihilo y debe ser diseñado para ser thread-safe.
Buenas prácticas de concurrencia en servlets
-
Hacer los servlets inmutables siempre que sea posible.
-
Delegar el manejo de estados en sistemas externos (base de datos, cachés distribuidas, etc.).
-
Utilizar sincronización de bajo nivel solo cuando sea estrictamente necesario, prefiriendo estructuras concurrentes ya probadas de
java.util.concurrent. -
No bloquear el hilo innecesariamente, ya que cada hilo bloqueado reduce la capacidad de respuesta global del servidor.
-
Para tareas pesadas o de larga duración, descargar el trabajo a otro hilo gestionado por un
ExecutorServiceen lugar de bloquear el hilo que atiende la petición.
Si quieres, puedo también integrar esta explicación dentro de la parte de “Los hilos están por todas partes” (4.1.4) del documento, para que tu versión ampliada quede cohesionada y con ejemplos prácticos de cada tecnología que se menciona ahí.
¿Quieres que lo haga?