- En el pasado, el uso de CPU de mi sistema llegó al 3,200%, saturando por completo los 32 núcleos
- Estaba usando el runtime de Java 17 y, al revisar el tiempo de CPU en un volcado de hilos y ordenar por tiempo de CPU, encontré muchos hilos similares
- Análisis del código problemático
- A través del stack trace, confirmé la línea 29 de la clase
BusinessLogic
- Ese código recorría la lista
unrelatedObjects mientras insertaba el valor de relatedObject en treeMap
- Era un código ineficiente porque no usaba
unrelatedObject dentro del bucle
Corrección del código y pruebas
- Se eliminó el bucle innecesario y se cambió por una sola línea:
treeMap.put(relatedObject.a(), relatedObject.b());
- Se ejecutaron pruebas unitarias antes y después del cambio, pero no fue posible reproducir el problema
- Incluso cuando
treeMap y unrelatedObjects tenían más de 1,000,000 de elementos cada uno, el problema no aparecía
Descubrimiento de la causa del problema
treeMap estaba siendo accedido de forma concurrente por varios hilos y no tenía sincronización
- El problema ocurría porque varios hilos modificaban
TreeMap al mismo tiempo
Reproducción del problema mediante un experimento
- Se realizó un experimento en el que varios hilos actualizaban aleatoriamente un
TreeMap compartido
- Se configuró un bloque
try-catch para ignorar NullPointerException
- Como resultado del experimento, se confirmó un fenómeno en el que el uso de CPU subía hasta 500%
Conclusión
- La modificación concurrente de un
TreeMap sin sincronización puede causar problemas graves de rendimiento
- Para evitar este tipo de problemas, se recomienda sincronizar
TreeMap o usar una colección thread-safe como ConcurrentMap
1 comentarios
Comentarios de Hacker News
Pensaba que una condición de carrera causaba corrupción de datos o deadlocks, pero no había considerado que también pudiera provocar problemas de rendimiento. Los datos pueden corromperse de una forma que genere un bucle infinito
TreeMapconCollections.synchronizedMapo cambiar aConcurrentHashMapy ordenar cuando sea necesarioTreeMapsea thread-safeTreeMap, o sugerir no usarTreeMapsi no se necesitan elementos ordenados. Pero en este caso no ocurrióTreeMapporHashMap, seguiría estando malEn código donde funcionan varios hilos, la única estrategia realmente segura es hacer que todos los objetos sean inmutables, y limitar los objetos que no puedan ser inmutables a secciones pequeñas, autocontenidas y estrictamente controladas
La mención de que "casi no se podía entrar por ssh" me recordó una situación de la época del posgrado, cuando usaban un Sun UltraSparc 170
El código podría reducirse simplemente a lo siguiente
treeMap.putcuando <i>unrelatedObjects</i> no está vacío. Eso también podría ser un bugtreeMaprealmente se comporta como un mapaOtra forma de obtener un bucle infinito es usar una implementación de <i>Comparator</i> o <i>Comparable</i> que no implemente un orden total consistente
Se podría considerar detectar ciclos usando un contador incremental y lanzar una excepción si supera la profundidad del árbol o el tamaño de la colección
En Java, realizar operaciones concurrentes sobre objetos que no son thread-safe produce los bugs más interesantes
Existe la pregunta de si un
TreeMapsin protección puede causar una utilización del 3,200%El autor encontró una especie de Poison Pill. Esto es más común en sistemas de event sourcing: un mensaje que mata todo lo que toca
Las excepciones en hilos son un problema absoluto
select()y hilos lanzando excepciones