Planteamiento y caso de uso
Un servicio Java debe pasar un ID de rastreo, un tenant o un principal de seguridad a callbacks profundos e hilos virtuales sin agregar parámetros a cada método ni filtrar estado a través de un pool reutilizado. Explique el alcance léxico de ScopedValue en Java 25, la vinculación y revinculación, Carrier, los hilos virtuales y la concurrencia estructurada, y luego defina el límite para migrar desde ThreadLocal.
Qué evalúa el entrevistador
- Explicar el rango visible y el ciclo de vida del contexto implícito.
- Entender que una vinculación de
ScopedValuesolo se puede leer dentro de un alcance derun,callowhere. - Distinguir la propagación de instantáneas inmutables frente al estado mutable local al hilo.
- Gestionar vinculaciones anidadas, tareas secundarias, excepciones y cancelaciones.
- Reconocer los riesgos asociados a principales de seguridad, objetos mutables y migración de pools.
Preguntas para aclarar primero
- ¿Es el contexto un valor de solo lectura con alcance de solicitud o un estado mutable que debe escribirse de vuelta entre hilos?
- ¿Están las tareas usando hilos virtuales, concurrencia estructurada o un pool tradicional?
- ¿Deben las tareas secundarias heredar el contexto y se les permite anular una clave?
- ¿Qué versiones de JDK son compatibles y se puede realizar la migración por etapas?
Respuesta en treinta segundos
Modele el contexto de la solicitud como un valor inmutable y cree un alcance léxico con ScopedValue.where(key, value).run o call. El código profundo lee a través de la clave y la vinculación desaparece cuando finaliza el alcance; los alcances anidados pueden revincular temporalmente, mientras que Carrier es inmutable y seguro para hilos (thread-safe). Esto otorga al código con hilos virtuales y concurrencia estructurada un ciclo de vida más estricto que el de un ThreadLocal mutable, pero no está pensado para estados que deban escribirse a través de distintos alcances. Conserve parámetros explícitos y pruebas de límites durante la migración.
Respuesta a fondo, paso a paso
1. Definir la clave de contexto
Utilice una clave ScopedValue privada y coloque el ID de rastreo, el ID de tenant y el principal en un Context inmutable. No exponga un mapa mutable ni un objeto de sesión modificable al código profundo.
2. Crear un alcance léxico
static final ScopedValue<RequestContext> REQUEST = ScopedValue.newInstance();
ScopedValue.where(REQUEST, context).run(() -> handle(request));Las llamadas profundas dentro de handle pueden leer la vinculación actual, pero esta no es visible después de que finaliza el alcance. El ciclo de vida sigue al bloque de código en lugar de a la recuperación del hilo.
3. Leer y manejar la ausencia
Use isBound() cuando la ausencia sea válida, o orElse para un valor predeterminado interno explícito. El contexto obligatorio, como un principal de seguridad, debe usar orElseThrow en lugar de ejecutarse silenciosamente como anónimo.
4. Explicar Carrier
ScopedValue.Carrier es un mapeo clave-valor inmutable y seguro para hilos; encadenar where devuelve un nuevo carrier. Ensamble varias vinculaciones e invoque run o call; no comparta un carrier mutable.
5. Manejar la revinculación anidada
Un where interno puede vincular temporalmente un nuevo valor para la misma clave, tras lo cual se restaura el valor externo. Dibuje el árbol de alcances durante la revisión para que los callbacks, manejadores de excepciones y logs no puedan leer el tenant o principal incorrecto.
6. Combinar con hilos virtuales y concurrencia estructurada
Cree tareas secundarias dentro del alcance léxico para que reciban la instantánea de contexto deseada. La cancelación o finalización excepcional igualmente sale del alcance sin necesidad de una limpieza manual de thread-local; documente qué tareas secundarias pueden anular una clave.
7. Comparar con ThreadLocal
ThreadLocal se adapta a APIs legadas que necesitan asociación con el hilo y escrituras mutables, pero la reutilización en pools hace que la limpieza sea fácil de olvidar. ScopedValue favorece una visibilidad de solo lectura, de corta duración y restringida, y no puede reemplazar todos los ThreadLocal, especialmente el estado que debe modificarse a través de callbacks.
8. Planificar la migración y observabilidad
Defina el esquema de contexto, los lectores permitidos y la matriz de JDK, y luego use un adaptador para que el código antiguo pueda migrar de forma incremental. Registre fallas por falta de vinculación, límites de hilos, anulaciones anidadas y referencias retenidas después de completar la solicitud para que el contexto implícito no se convierta en una variable global invisible.
Compensaciones y límites
ScopedValue pasa una referencia vinculada; si el valor es mutable, las condiciones de carrera de datos y los cambios de privilegios siguen siendo posibles. No otorga semántica de negocio automáticamente a hilos nuevos arbitrarios ni reemplaza parámetros explícitos, políticas de autenticación o protocolos de cancelación. Adopte la API de Java 25 con una línea base de JDK clara y un modelo de concurrencia estructurada, manteniendo una ruta de compatibilidad para versiones anteriores.
Plan de implementación y evidencia
- Haga un inventario de claves de ThreadLocal existentes, escritores, lógica de limpieza y límites de hilos.
- Cree claves de ScopedValue y tipos inmutables para el contexto de solicitud de solo lectura.
- Envuelva los puntos de entrada de solicitudes y la creación de tareas estructuradas con
where(...).run/call. - Pruebe revinculaciones anidadas, excepciones, cancelaciones, hilos virtuales, reutilización de pools y acceso sin vinculación.
- Utilice la guía de la API de Java 25 de Oracle sobre
ScopedValue,Carrier, control de acceso y concurrencia estructurada como evidencia de lanzamiento.
Errores comunes y seguimiento
Error 1: Tratar a ScopedValue como un global mutable
La clave controla la visibilidad, pero el objeto vinculado aún puede ser mutable. Utilice contexto inmutable y restrinja las escrituras.
Error 2: Olvidar el límite del alcance
Leer después de salir del alcance falla o devuelve una vinculación externa. Identifique explícitamente los puntos de entrada, los callbacks y la creación de tareas asíncronas.
Error 3: Reemplazar cada ThreadLocal directamente
Las escrituras entre alcances y el soporte para versiones anteriores de JDK tienen diferentes restricciones. Clasifique los usos y luego migre primero el contexto de solicitud de solo lectura.
Error 4: Asumir que cada hilo hereda automáticamente
La propagación de contexto debe diseñarse junto con la creación de tareas, ejecutores y concurrencia estructurada; el nombre de un hilo no constituye un contrato de propagación.
Error 5: Ignorar la anulación del principal
La revinculación anidada puede cambiar la identidad de auditoría. Mantenga el principal inmutable, requiéralo cuando sea necesario y audite las anulaciones.