Saltar al contenido principal

Seguridad de hilos

Que algo no sea thread-safe (no seguro para hilos) significa que un objeto, variable o método no está preparado para ser utilizado por múltiples hilos (threads) al mismo tiempo.

Si varios hilos intentan acceder y, sobre todo, modificar ese recurso simultáneamente, el estado de los datos se corromperá, produciendo resultados impredecibles, errores intermitentes o bloqueos.

Para entenderlo mejor, vamos a ver la explicación de por qué ocurre esto, ejemplos comunes y cómo se soluciona.

Race condition

El problema más común cuando algo no es thread-safe se conoce como condición de carrera (race condition).

Imagina una operación muy sencilla, como sumar 1 a un contador: contador++. Aunque en Java parece una sola instrucción, el procesador en realidad hace tres cosas:

  1. Lee el valor actual del contador (ej. 5).
  2. Suma 1 a ese valor (5 + 1 = 6).
  3. Guarda el nuevo valor en la variable (6).

Si el contador no es thread-safe, y dos hilos (llamémosles A y B) intentan hacer contador++ exactamente al mismo tiempo, puede pasar lo siguiente:

  • El A lee el valor: 5.
  • El B lee el valor: 5 (antes de que el A haya guardado nada).
  • El A suma 1 y guarda el valor: 6.
  • El B suma 1 y guarda el valor: 6.

Aunque dos hilos intentaron sumar 1 (lo que debería haber resultado en 7), el resultado final es 6. Se ha perdido información porque la operación no estaba protegida.

Ejemplos comunes en Java

La biblioteca estándar de Java tiene muchas clases que no son thread-safe por defecto, ya que hacerlas seguras consume más recursos (hace que el programa vaya más lento). Es tu responsabilidad protegerlas si las vas a usar en un entorno multihilo.

  • Colecciones básicas: ArrayList, HashMap, HashSet no son thread-safe. Si un hilo está leyendo un ArrayList mientras otro añade un elemento, Java probablemente lanzará una excepción ConcurrentModificationException.
  • Manipulación de texto: StringBuilder no es thread-safe (por eso es muy rápido).
  • Fechas antiguas: SimpleDateFormat es famosamente no thread-safe, y usar la misma instancia en varios hilos causa errores desastrosos al formatear fechas.

¿Cómo se hace algo thread-safe?

Si necesitas que varios hilos trabajen con la misma información, tienes varias herramientas en Java para evitar el caos:

  1. Sincronización (synchronized): Puedes usar esta palabra clave en métodos o bloques de código. Esto crea un "candado" (lock) que obliga a los hilos a hacer fila; solo un hilo puede ejecutar ese código a la vez.
  2. Clases Atómicas: Java ofrece clases en el paquete java.util.concurrent.atomic (como AtomicInteger o AtomicBoolean) que realizan operaciones como el contador++ de forma segura en un solo paso indivisible.
  3. Colecciones Concurrentes: En lugar de HashMap o ArrayList, puedes usar ConcurrentHashMap o CopyOnWriteArrayList, que están diseñadas específicamente para funcionar perfectamente con múltiples hilos.
  4. Alternativas seguras: Usar StringBuffer en lugar de StringBuilder, o las nuevas clases de fechas de Java 8 (java.time.LocalDate, DateTimeFormatter) que son inmutables y, por tanto, siempre thread-safe.