Consigna y caso de uso
Mantienes un servicio altamente concurrente que debe crear de forma perezosa un objeto de configuración costoso y publicar un resultado exitoso solo una vez. Utiliza Java 25 StableValue y explica la concurrencia, los fallos, la observabilidad, el despliegue de características preview y los límites de rollback. La consigna evalúa la concurrencia en Java, el razonamiento sobre la publicación según el JMM y el criterio acerca de las API modernas de JDK.
Qué está evaluando el entrevistador
- Si identificas correctamente StableValue como una API preview de Java SE 25 en lugar de un contrato estable a largo plazo.
- Si distingues un contenedor de escritura única de un objeto inmutable.
- Si puedes explicar el comportamiento ante fallos y reintentos para
orElseSet,trySet,setOrThrowyorElseThrow. - Si manejas excepciones de inicialización, inicializadores competidores, el apagado y las actualizaciones.
Preguntas para aclarar antes de responder
- Tras un fallo en la inicialización, ¿puede el sistema reintentar, o el primer fallo activa un disyuntor (circuit breaker)?
- ¿Tiene efectos secundarios la construcción y deben los invocadores competidores esperar un único resultado compartido?
- ¿Puede el entorno de ejecución de destino habilitar características preview y la política de lanzamientos permite API preview?
- ¿Es el objeto verdaderamente inmutable o requiere encapsulación y gestión del ciclo de vida adicionales?
Un marco de respuesta de 30 segundos
Trataría StableValue como un contenedor que puede establecerse con éxito como máximo una vez, no como una garantía automática de seguridad para hilos (thread-safety) para el objeto que contiene. Construiría y validaría completamente el valor para luego publicarlo con orElseSet; los lectores competidores consumen el valor publicado. Si los fallos son reintentables, definiría límites de reintento, clases de excepciones y limpieza de efectos secundarios. Si el fallo es terminal, utilizaría setOrThrow con validación en el inicio. Dado que JDK 25 aún clasifica esta API como preview, el plan de despliegue debe incluir flags de habilitación, monitoreo, rollback y pruebas de actualización.
Análisis detallado paso a paso
1. El contrato de StableValue
StableValue almacena un único valor no nulo y permite establecerlo con éxito como máximo una vez. Ofrece operaciones de lectura, intento de asignación (try-set) y asignación con lanzamiento de excepciones para que el runtime pueda reconocer un valor estable y optimizar la publicación segura.
2. Diferencia respecto a volatile y AtomicReference
volatile permite escrituras repetidas y AtomicReference admite actualizaciones y compare-and-set. StableValue expresa directamente "nunca cambia tras establecerse con éxito". Se adapta a configuraciones de una sola vez, resultados parseados o identificadores compartidos, no a cachés actualizables o reemplazables.
3. Código de inicialización perezosa de una sola vez
import java.lang.StableValue;
final class CatalogHolder {
private final StableValue<Catalog> catalog = StableValue.of();
Catalog get() {
return catalog.orElseSet(this::loadCatalog);
}
private Catalog loadCatalog() {
return Catalog.loadFrom("/etc/catalog.json");
}
}La factoría debe completar cada validación antes de retornar. Nunca publiques un objeto parcialmente inicializado para mutarlo después.
4. Manejo de inicializaciones competidoras
Cuando varios hilos llaman a orElseSet, solo un valor se publica con éxito y los demás deben leer ese valor. La factoría puede ejecutarse de forma concurrente, por lo que debes asumir que puede ejecutarse más de una vez y evitar efectos secundarios irreversibles como registros duplicados, cobros o creación de archivos.
5. Política de excepciones y reintentos
Si la factoría lanza una excepción, no se instala ningún valor y una llamada posterior puede intentarlo de nuevo. Separa los fallos transitorios de los errores deterministas de configuración, y luego define backoff, límites de intentos y métricas. Si los reintentos están prohibidos, captura el fallo de inicio y usa setOrThrow o termina el proceso deliberadamente.
6. Cuándo usar trySet y setOrThrow
trySet informa si este intento ganó, lo cual resulta adecuado para un control explícito de la contención. setOrThrow lanza una excepción cuando ya está establecido o cuando el valor no es válido, lo cual conviene a una ruta de inicio con un único escritor garantizado. Un lector puede usar orElseThrow para indicar que la inicialización ya debe haber ocurrido.
7. Visibilidad de la publicación y estado del objeto
El contenedor define cuándo se hace visible un valor; no hace que el objeto sea internamente seguro para hilos. Mantén los campos de Catalog sin cambios tras la construcción mediante campos final, colecciones inmutables o sincronización controlada. Si posee recursos mutables, define protocolos de cierre y acceso concurrente.
8. Ingeniería del despliegue de una API preview
JDK 25 documenta StableValue como preview. La compilación y la ejecución requieren las opciones de preview correspondientes, manteniéndose consistentes en CI, imágenes de contenedor, IDE y scripts de producción. Registra una implementación de respaldo (fallback), una matriz de compatibilidad y pasos de rollback para actualizaciones antes de exponer la API preview en un límite irreversible.
Compensaciones y límites
- Usa una caché o referencia atómica cuando los valores deban refrescarse, reemplazarse o desalojarse; StableValue no posee dicha operación de ciclo de vida.
- Si la factoría tiene efectos secundarios externos, las llamadas competidoras pueden repetirlos. Haz que la operación sea idempotente o coordínala con un bloqueo separado antes de publicar.
- Si los invocadores deben esperar la inicialización, usa un Future en una capa superior; no asumas que StableValue proporciona coordinación bloqueante.
- Las afirmaciones sobre el rendimiento en preview requieren benchmarks que cubran arranque en frío, contención, excepciones y GC.
Plan de implementación y evidencia
- Compila una implementación mínima con las características preview de JDK 25 habilitadas y verifica los valores de retorno y excepciones de
orElseSet,trySetysetOrThrow. - Ejecuta pruebas de estrés de concurrencia midiendo las invocaciones de la factoría, publicaciones exitosas, efectos secundarios duplicados y latencia de lectura.
- Inyecta excepciones de construcción y tiempos de espera para verificar el reintento, backoff, alertas y comportamiento de salida del proceso.
- Valida los flags de inicio, el JDK del contenedor, el monitoreo y los scripts de rollback en el runtime de destino.
- Usa la API de Oracle, las notas de migración de JDK 25 y la especificación de la JVM como evidencia de revisión.
Errores comunes y preguntas de seguimiento
Error 1: Llamar objeto inmutable a StableValue
Limita las escrituras exitosas al contenedor; no congela el objeto en su interior. Explica el diseño de inmutabilidad propio del objeto.
Error 2: Asumir que la factoría se ejecuta una sola vez
La contención o los reintentos fallidos pueden ejecutarla varias veces. Explica los efectos secundarios idempotentes y la verificación del conteo de invocaciones.
Error 3: Ignorar la habilitación de preview
Compilar localmente en JDK 25 no demuestra que esté listo para producción. Verifica los flags de preview en la compilación, inicio, contenedores y CI.
Pregunta de seguimiento: ¿Por qué no mantener el bloqueo de doble comprobación con volatile?
Volatile sigue siendo adecuado para una referencia actualizable. Para una publicación explícita de una sola vez, StableValue comunica mejor la intención, pero se debe asumir el riesgo de ser preview y demostrar cualquier beneficio con benchmarks.
Pregunta de seguimiento: ¿Cómo demuestras que no hay efectos secundarios duplicados?
Agrega contadores observables y claves de idempotencia a la factoría, y luego verifica los conteos, identificadores de recursos y la consistencia del valor final bajo contención, excepciones y reintentos.