Nos cambiamos a hilos virtuales de Java 21 y obtuvimos un interbloqueo en TPC-C para Postgres
Los nuevos hilos virtuales de Java 21 están sacando a la luz riesgos sutiles de interbloqueo en código Java existente, especialmente cuando primitivas de sincronización antiguas como `synchronized` y `Object.wait()` se usan dentro de bibliotecas como los pools de conexiones JDBC. Los comentaristas señalan que estos constructos pueden “pinnear” los hilos portadores y agotar el pool subyacente limitado, rompiendo programas que eran seguros con muchos hilos OS. El consenso es que los hilos virtuales son prometedores para cargas intensivas en E/S, pero todavía no son una sustitución directa en todas partes, y que los desarrolladores deberían auditar bibliotecas, preferir utilidades de concurrencia modernas (locks, semáforos, colecciones concurrentes) y ajustar o esperar correcciones del runtime antes de una adopción amplia.
Naturaleza del interbloqueo
- Hay consenso en que los hilos virtuales no “crearon mágicamente” una nueva clase de interbloqueo; el benchmark puso de manifiesto un problema de diseño existente bajo nuevas restricciones.
- El interbloqueo surgió porque los hilos virtuales quedaron bloqueados dentro del código del pool de conexiones
c3p0usandosynchronized+Object.wait, agotando los carrier threads. - La solución del artículo —proteger las conexiones a la BD con un
Semaphore— evita que los hilos virtuales entren en c3p0 hasta que haya una conexión disponible, de modo que se bloqueen en código consciente de los VT en lugar de dentro del pool. - Varios comentaristas subrayan que se trata de un problema genérico de JDBC/pool de conexiones, no específico de PostgreSQL.
Hilos virtuales, pinning y progreso hacia adelante
- Los hilos virtuales no pueden desmontarse cuando:
- Se ejecutan dentro de bloques/métodos
synchronized, o - Ejecutan código nativo/externo, o ciertas llamadas bloqueantes heredadas como
Object.wait.
- Se ejecutan dentro de bloques/métodos
- En estos casos, el hilo portador (OS) queda “pinneado”; si ocurren muchas de estas llamadas, el pool de carrier threads del programador de VT (por defecto máximo 256, configurable mediante
jdk.virtualThreadScheduler.maxPoolSize) puede agotarse. - Algunos aclaran que
Object.waittanto libera el monitor como está implementado en código nativo, así que el problema no es solosynchronized, sino también quewaiten sí no coopera con la planificación de VT. - La preocupación no es tanto la corrección de los locks, sino la pérdida de “progreso hacia adelante” cuando todos los carriers están bloqueados.
“Sustitución directa” vs. nuevas semánticas
- La documentación y algunos mensajes presentaron los hilos virtuales como algo casi intercambiable; otros señalan la guía de adopción, que advierte explícitamente contra cambiar ciegamente todos los hilos de plataforma por VT.
- Un bando: si tu diseño requiere más hilos ejecutables simultáneamente que carriers, el diseño era frágil; VT solo lo pone en evidencia.
- El otro bando: el código anterior era correcto bajo la suposición de que el SO siempre podía crear más hilos; introducir un límite duro de carriers cambia silenciosamente el comportamiento del sistema y puede romper ese código.
- Hay desacuerdo sobre si la documentación advirtió adecuadamente sobre interbloqueos frente a meros problemas de “escalabilidad”.
Soluciones alternativas y buenas prácticas discutidas
- Sustituir patrones
synchronized/wait/notifypor:- Bloqueos, semáforos y conditions de
java.util.concurrent. - Colas concurrentes para patrones productor–consumidor.
- Bloqueos, semáforos y conditions de
- Evitar operaciones largas o bloqueantes dentro de
synchronized; considerarlo mala práctica incluso sin VT. - Usar pools de carrier threads y configuración:
- Aumentar
jdk.virtualThreadScheduler.maxPoolSizecuando sea apropiado. - Descargar llamadas CPU-bound o nativas a ejecutores de hilos de plataforma.
- Aumentar
- Usar herramientas de diagnóstico como
-Djdk.tracePinnedThreadspara encontrar zonas problemáticas. - Algunos sugieren cambiar pools antiguos como c3p0 por otros mantenidos activamente (p. ej., HikariCP), aunque incluso los pools modernos siguen adaptándose a los VT.
Madurez de los hilos virtuales y futuras correcciones
- Muchos consideran que los hilos virtuales son potentes, pero aún no son de “configúralo y olvídate”; funcionan mejor para código moderno, intensivo en E/S, que evita primitivas heredadas.
- Varios señalan que el comportamiento de
synchronized/waites una limitación de implementación conocida y temporal, y que el equipo del JDK está trabajando para hacerlos compatibles con VT. - Algunos recomiendan esperar a un futuro LTS en el que
synchronizedyObject.waitcooperen plenamente con los hilos virtuales antes de adoptarlos ampliamente, especialmente en bases de código con muchas bibliotecas de terceros.
Comparaciones y metadiscusión
- Se trazan comparaciones con Go, Erlang, Node.js, Python async y el pool de hilos/async de .NET: todos tuvieron rarezas iniciales al mezclar modelos bloqueantes y no bloqueantes.
- Otras discusiones cuestionan si el threading verde/M:N puede llegar a ser realmente transparente en ecosistemas construidos alrededor de hilos del SO.
- Menores desvíos tratan sobre el editorialismo del título en HN y el rechazo generalizado a las imágenes hero generadas por IA en publicaciones técnicas.